feat: enhance telemetry for action download resolution and failures (#4536)

This commit is contained in:
Tingluo Huang
2026-07-01 17:29:02 -04:00
committed by GitHub
parent 1ed4f70ee9
commit 4c6d85cfc0
2 changed files with 168 additions and 6 deletions

View File

@@ -228,7 +228,30 @@ namespace GitHub.Runner.Worker
{
throw new Exception($"Missing download info for {lookupKey}");
}
await DownloadRepositoryActionAsync(executionContext, downloadInfo);
Exception downloadFailure = null;
try
{
await DownloadRepositoryActionAsync(executionContext, downloadInfo);
}
catch (Exception ex)
{
// record the exception for telemetry, and rethrow the original exception to fail the step.
downloadFailure = ex;
throw;
}
finally
{
executionContext.Global.JobTelemetry.Add(new JobTelemetry()
{
Type = JobTelemetryType.General,
Message = $"resolve_download_actions_telemetry:{StringUtil.ConvertToJson(new ActionTelemetryPayload
{
Operation = "download_action",
Result = downloadFailure == null ? "succeeded" : downloadFailure.GetType().Name
}, Newtonsoft.Json.Formatting.None)}"
});
}
}
// Parse action.yml and collect composite sub-actions for batched
@@ -398,7 +421,30 @@ namespace GitHub.Runner.Worker
if (repositoryActions.Count > 0)
{
// Get the download info
var downloadInfos = await GetDownloadInfoAsync(executionContext, repositoryActions);
IDictionary<string, WebApi.ActionDownloadInfo> downloadInfos = null;
Exception resolveFailure = null;
try
{
downloadInfos = await GetDownloadInfoAsync(executionContext, repositoryActions);
}
catch (Exception ex)
{
// record the exception for telemetry, and rethrow the original exception to fail the step.
resolveFailure = ex;
throw;
}
finally
{
executionContext.Global.JobTelemetry.Add(new JobTelemetry()
{
Type = JobTelemetryType.General,
Message = $"resolve_download_actions_telemetry:{StringUtil.ConvertToJson(new ActionTelemetryPayload
{
Operation = "resolve_actions",
Result = resolveFailure == null ? "succeeded" : resolveFailure.GetType().Name
}, Newtonsoft.Json.Formatting.None)}"
});
}
// Download each action
foreach (var action in repositoryActions)
@@ -414,7 +460,29 @@ namespace GitHub.Runner.Worker
throw new Exception($"Missing download info for {lookupKey}");
}
await DownloadRepositoryActionAsync(executionContext, downloadInfo);
Exception downloadFailure = null;
try
{
await DownloadRepositoryActionAsync(executionContext, downloadInfo);
}
catch (Exception ex)
{
// record the exception for telemetry, and rethrow the original exception to fail the step.
downloadFailure = ex;
throw;
}
finally
{
executionContext.Global.JobTelemetry.Add(new JobTelemetry()
{
Type = JobTelemetryType.General,
Message = $"resolve_download_actions_telemetry:{StringUtil.ConvertToJson(new ActionTelemetryPayload
{
Operation = "download_action",
Result = downloadFailure == null ? "succeeded" : downloadFailure.GetType().Name
}, Newtonsoft.Json.Formatting.None)}"
});
}
}
// More preparation based on content in the repository (action.yml)
@@ -980,10 +1048,33 @@ namespace GitHub.Runner.Worker
if (actionsToResolve.Count > 0)
{
var downloadInfos = await GetDownloadInfoAsync(executionContext, actionsToResolve);
foreach (var kvp in downloadInfos)
IDictionary<string, WebApi.ActionDownloadInfo> downloadInfos = null;
Exception resolveFailure = null;
try
{
resolvedDownloadInfos[kvp.Key] = kvp.Value;
downloadInfos = await GetDownloadInfoAsync(executionContext, actionsToResolve);
foreach (var kvp in downloadInfos)
{
resolvedDownloadInfos[kvp.Key] = kvp.Value;
}
}
catch (Exception ex)
{
// record the exception for telemetry, and rethrow the original exception to fail the step.
resolveFailure = ex;
throw;
}
finally
{
executionContext.Global.JobTelemetry.Add(new JobTelemetry()
{
Type = JobTelemetryType.General,
Message = $"resolve_download_actions_telemetry:{StringUtil.ConvertToJson(new ActionTelemetryPayload
{
Operation = "resolve_actions",
Result = resolveFailure == null ? "succeeded" : resolveFailure.GetType().Name
}, Newtonsoft.Json.Formatting.None)}"
});
}
}
}
@@ -1209,6 +1300,12 @@ namespace GitHub.Runner.Worker
private string GetWatermarkFilePath(string directory) => directory + ".completed";
private sealed class ActionTelemetryPayload
{
public string Operation { get; set; }
public string Result { get; set; }
}
private ActionSetupInfo PrepareRepositoryActionAsync(IExecutionContext executionContext, Pipelines.ActionStep repositoryAction)
{
var repositoryReference = repositoryAction.Reference as Pipelines.RepositoryPathReference;

View File

@@ -90,6 +90,11 @@ namespace GitHub.Runner.Common.Tests.Worker
var actionYamlFile = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), ActionName, "main", "action.yml");
Assert.True(File.Exists(actionYamlFile));
var telemetryMessages = GetTelemetryMessages();
Assert.True(ContainsTelemetry(telemetryMessages, "resolve_actions"));
Assert.True(ContainsTelemetry(telemetryMessages, "succeeded"));
Assert.True(ContainsTelemetry(telemetryMessages, "download_action"));
_hc.GetTrace().Info(File.ReadAllText(actionYamlFile));
}
finally
@@ -148,6 +153,11 @@ namespace GitHub.Runner.Common.Tests.Worker
// Act + Assert
await Assert.ThrowsAsync<InvalidActionArchiveException>(async () => await _actionManager.PrepareActionsAsync(_ec.Object, actions));
var telemetryMessages = GetTelemetryMessages();
Assert.True(ContainsTelemetry(telemetryMessages, "resolve_actions"));
Assert.True(ContainsTelemetry(telemetryMessages, "download_action"));
Assert.True(ContainsTelemetry(telemetryMessages, "InvalidActionArchiveException"));
}
finally
{
@@ -215,6 +225,51 @@ namespace GitHub.Runner.Common.Tests.Worker
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public async Task PrepareActions_ResolveActionDownloadInfo_RecordsTelemetry_OnFailure()
{
try
{
// Arrange
Setup();
_ec.Object.Global.Variables.Set(Constants.Variables.System.JobRequestType, "RunnerJobRequest");
_launchServer
.Setup(x => x.ResolveActionsDownloadInfoAsync(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<ActionReferenceList>(), It.IsAny<CancellationToken>(), It.IsAny<bool>()))
.ThrowsAsync(new Exception("resolve failed"));
var actions = new List<Pipelines.JobStep>
{
new Pipelines.ActionStep()
{
Name = "action",
Id = Guid.NewGuid(),
Reference = new Pipelines.RepositoryPathReference()
{
Name = "actions/checkout",
Ref = "v4",
RepositoryType = "GitHub"
}
}
};
// Act + Assert
await Assert.ThrowsAsync<FailedToResolveActionDownloadInfoException>(async () => await _actionManager.PrepareActionsAsync(_ec.Object, actions));
var telemetryMessages = GetTelemetryMessages();
Assert.Equal(1, telemetryMessages.Count(message =>
message.Contains("resolve_actions", StringComparison.OrdinalIgnoreCase)
&& !message.Contains("\"result\":\"succeeded\"", StringComparison.OrdinalIgnoreCase)));
Assert.False(ContainsTelemetry(telemetryMessages, "resolve_actions\",\"result\":\"succeeded"));
}
finally
{
Teardown();
}
}
#if OS_LINUX
[Fact]
[Trait("Level", "L0")]
@@ -3333,6 +3388,16 @@ runs:
}
}
private IList<string> GetTelemetryMessages()
{
return _ec.Object.Global.JobTelemetry.Select(x => x.Message).ToList();
}
private static bool ContainsTelemetry(IList<string> telemetryMessages, string expectedFragment)
{
return telemetryMessages.Any(message => message.Contains(expectedFragment, StringComparison.OrdinalIgnoreCase));
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]