From 6b1bbc140674ee8a12158cdd77a988afd5c0c455 Mon Sep 17 00:00:00 2001 From: Sanchit Mehta Date: Thu, 12 Nov 2020 07:28:05 -0800 Subject: [PATCH] Added tracing for completed deployment --- Common/Constants.cs | 4 +- Kudu.Console/Program.cs | 3 + Kudu.Core/Deployment/DeploymentManager.cs | 3 + Kudu.Core/Helpers/DeploymentCompletedInfo.cs | 71 +++++++++++++++++++ Kudu.Core/Helpers/PostDeploymentHelper.cs | 21 ++++++ Kudu.Core/Tracing/IAnalytics.cs | 2 + Kudu.Core/Tracing/KuduEvent.cs | 2 + Kudu.Core/Tracing/KuduEventGenerator.cs | 1 + Kudu.Core/Tracing/KuduEventSource.cs | 18 +++++ .../Tracing/LinuxContainerEventGenerator.cs | 13 ++++ Kudu.Core/Tracing/Log4NetEventGenerator.cs | 13 ++++ .../Deployment/DeploymentController.cs | 3 - .../Deployment/PushDeploymentController.cs | 10 +-- 13 files changed, 156 insertions(+), 8 deletions(-) create mode 100644 Kudu.Core/Helpers/DeploymentCompletedInfo.cs diff --git a/Common/Constants.cs b/Common/Constants.cs index b2443bfa..a658b862 100644 --- a/Common/Constants.cs +++ b/Common/Constants.cs @@ -55,7 +55,9 @@ public static class Constants public const string ContinuousPath = "continuous"; public const string TriggeredPath = "triggered"; - public const string DummyRazorExtension = ".kudu777"; + public const string DummyRazorExtension = ".kudu777"; + + public const string ScmDeploymentKind = "ScmDeploymentKind"; // Kudu trace text file related public const string DeploymentTracePath = LogFilesPath + @"/kudu/deployment"; diff --git a/Kudu.Console/Program.cs b/Kudu.Console/Program.cs index 3684a8b5..e9946cb2 100644 --- a/Kudu.Console/Program.cs +++ b/Kudu.Console/Program.cs @@ -42,6 +42,9 @@ private static int Main(string[] args) log4net.Config.XmlConfigurator.Configure(repo, log4netConfig["log4net"]); } + // signify the deployment is done by git push + System.Environment.SetEnvironmentVariable(Constants.ScmDeploymentKind, "GitPush"); + // Turn flag on in app.config to wait for debugger on launch if (ConfigurationManager.AppSettings["WaitForDebuggerOnStart"] == "true") { diff --git a/Kudu.Core/Deployment/DeploymentManager.cs b/Kudu.Core/Deployment/DeploymentManager.cs index e04b991b..21698a90 100644 --- a/Kudu.Core/Deployment/DeploymentManager.cs +++ b/Kudu.Core/Deployment/DeploymentManager.cs @@ -406,6 +406,9 @@ private void MarkStatusComplete(IDeploymentStatusFile status, bool success) // Cleaup old deployments PurgeAndGetDeployments(); + + // Report deployment completion + DeploymentCompletedInfo.Persist(_environment.RequestId, status, _analytics); } // since the expensive part (reading all files) is done, diff --git a/Kudu.Core/Helpers/DeploymentCompletedInfo.cs b/Kudu.Core/Helpers/DeploymentCompletedInfo.cs new file mode 100644 index 00000000..7ea257dd --- /dev/null +++ b/Kudu.Core/Helpers/DeploymentCompletedInfo.cs @@ -0,0 +1,71 @@ +using System; +using System.IO; +using Kudu.Core.Deployment; +using Kudu.Core.Infrastructure; +using Kudu.Core.Tracing; +using Newtonsoft.Json; + +namespace Kudu.Core.Helpers +{ + public class DeploymentCompletedInfo + { + public const string LatestDeploymentFile = "LatestDeployment.json"; + + public string TimeStamp { get; set; } + public string SiteName { get; set; } + public string RequestId { get; set; } + public string Kind { get; set; } + public string Status { get; set; } + public string Details { get; set; } + + private static IAnalytics _analytics; + + public static void Persist(string requestId, IDeploymentStatusFile status, IAnalytics analytics) + { + // signify the deployment is done by git push + var kind = System.Environment.GetEnvironmentVariable(Constants.ScmDeploymentKind); + if (string.IsNullOrEmpty(kind)) + { + kind = status.Deployer; + } + _analytics = analytics; + var serializedStatus = JsonConvert.SerializeObject(status, Formatting.Indented); + Persist(status.SiteName, kind, requestId, status.Status.ToString(), serializedStatus); + } + + public static void Persist(string siteName, string kind, string requestId, string status, string details) + { + var info = new DeploymentCompletedInfo + { + TimeStamp = $"{DateTime.UtcNow:s}Z", + SiteName = siteName, + Kind = kind, + RequestId = requestId, + Status = status, + Details = details ?? string.Empty + }; + + try + { + var path = Path.Combine(System.Environment.ExpandEnvironmentVariables(@"%HOME%"), "site", "deployments"); + var file = Path.Combine(path, $"{Constants.LatestDeployment}.json"); + var content = JsonConvert.SerializeObject(info, Formatting.Indented); + FileSystemHelpers.EnsureDirectory(path); + + // write deployment info to %home%\site\deployments\LatestDeployment.json + OperationManager.Attempt(() => FileSystemHelpers.Instance.File.WriteAllText(file, content)); + + _analytics.DeploymentCompleted( + info.SiteName, + info.Kind, + info.RequestId, + info.Status, + info.Details); + } + catch (Exception ex) + { + _analytics.UnexpectedException(ex); + } + } + } +} \ No newline at end of file diff --git a/Kudu.Core/Helpers/PostDeploymentHelper.cs b/Kudu.Core/Helpers/PostDeploymentHelper.cs index d6fc978f..2c1c9205 100644 --- a/Kudu.Core/Helpers/PostDeploymentHelper.cs +++ b/Kudu.Core/Helpers/PostDeploymentHelper.cs @@ -868,6 +868,27 @@ private static IEnumerable GetPostBuildActionScripts() || f.EndsWith(".bat", StringComparison.OrdinalIgnoreCase) || f.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) .OrderBy(n => n); + } + + /// + /// This common codes is to invoke post deployment operations. + /// It is written to require least dependencies but framework assemblies. + /// Caller is responsible for synchronization. + /// + /// WEBSITE_SITE_NAME env + /// MSDeploy, ZipDeploy, Git, .. + /// for correlation + /// Success or fail + /// deployment specific json + /// tracing + public static async Task InvokeWithDetails(string kind, string requestId, string status, string details, TraceListener tracer) + { + DeploymentCompletedInfo.Persist(System.Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME"), kind, requestId, status, details); + + if (string.Equals("Success", status, StringComparison.OrdinalIgnoreCase)) + { + await Invoke(requestId, tracer); + } } private static void Trace(TraceEventType eventType, string message) diff --git a/Kudu.Core/Tracing/IAnalytics.cs b/Kudu.Core/Tracing/IAnalytics.cs index a3904c57..23b61e5f 100644 --- a/Kudu.Core/Tracing/IAnalytics.cs +++ b/Kudu.Core/Tracing/IAnalytics.cs @@ -18,5 +18,7 @@ public interface IAnalytics void DeprecatedApiUsed(string route, string userAgent, string method, string path); void SiteExtensionEvent(string method, string path, string result, string deploymentDurationInMilliseconds, string Message); + + void DeploymentCompleted(string siteName, string kind, string requestId, string status, string details); } } diff --git a/Kudu.Core/Tracing/KuduEvent.cs b/Kudu.Core/Tracing/KuduEvent.cs index db894ba7..a8d85f2e 100644 --- a/Kudu.Core/Tracing/KuduEvent.cs +++ b/Kudu.Core/Tracing/KuduEvent.cs @@ -32,6 +32,8 @@ public class KuduEvent public string verb = string.Empty; public int statusCode = 0; public long latencyInMilliseconds = 0; + public string deploymentDetails = string.Empty; + public string deploymentStatus = string.Empty; public override string ToString() { diff --git a/Kudu.Core/Tracing/KuduEventGenerator.cs b/Kudu.Core/Tracing/KuduEventGenerator.cs index 9118a9e8..c751ff01 100644 --- a/Kudu.Core/Tracing/KuduEventGenerator.cs +++ b/Kudu.Core/Tracing/KuduEventGenerator.cs @@ -33,6 +33,7 @@ public static IKuduEventGenerator Log(ISystemEnvironment systemEnvironment = nul } else { + // Log4Net logger output to a file when running on Linux _eventGenerator = new Log4NetEventGenerator(); } } diff --git a/Kudu.Core/Tracing/KuduEventSource.cs b/Kudu.Core/Tracing/KuduEventSource.cs index c13d33f6..a9646020 100644 --- a/Kudu.Core/Tracing/KuduEventSource.cs +++ b/Kudu.Core/Tracing/KuduEventSource.cs @@ -72,6 +72,24 @@ public void GenericEvent(string siteName, string Message, string requestId, stri } } + /// + /// DeploymentCompleted event + /// + /// WEBSITE_SITE_NAME + /// MSDeploy, ZipDeploy, Git, ... + /// requestId + /// Success, Failed + /// deployment-specific json + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters")] + [Event(65516, Level = EventLevel.Informational, Message = "Deployment completed for site {0}", Channel = EventChannel.Operational)] + public void DeploymentCompleted(string siteName, string kind, string requestId, string status, string details) + { + if (IsEnabled()) + { + WriteEvent(65516, siteName, kind, requestId, status, details); + } + } + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters")] [Event(65515, Level = EventLevel.Informational, Message = "Api event for site {0}", Channel = EventChannel.Operational)] public void ApiEvent(string siteName, string Message, string address, string verb, string requestId, int statusCode, long latencyInMilliseconds, string userAgent) diff --git a/Kudu.Core/Tracing/LinuxContainerEventGenerator.cs b/Kudu.Core/Tracing/LinuxContainerEventGenerator.cs index 8cc0bec2..e48d209b 100644 --- a/Kudu.Core/Tracing/LinuxContainerEventGenerator.cs +++ b/Kudu.Core/Tracing/LinuxContainerEventGenerator.cs @@ -32,6 +32,19 @@ public void ProjectDeployed(string siteName, string projectType, string result, LogKuduTraceEvent(kuduEvent); } + public void DeploymentCompleted(string siteName, string kind, string requestId, string status, string details) + { + KuduEvent kuduEvent = new KuduEvent + { + siteName = siteName, + deploymentDetails = details, + deploymentStatus = status, + requestId = requestId, + }; + + LogKuduTraceEvent(kuduEvent); + } + public void WebJobStarted(string siteName, string jobName, string scriptExtension, string jobType, string siteMode, string error, string trigger) { KuduEvent kuduEvent = new KuduEvent diff --git a/Kudu.Core/Tracing/Log4NetEventGenerator.cs b/Kudu.Core/Tracing/Log4NetEventGenerator.cs index d7810324..0623e6c9 100644 --- a/Kudu.Core/Tracing/Log4NetEventGenerator.cs +++ b/Kudu.Core/Tracing/Log4NetEventGenerator.cs @@ -33,6 +33,19 @@ public void ProjectDeployed(string siteName, string projectType, string result, LogKuduTraceEvent(kuduEvent); } + public void DeploymentCompleted(string siteName, string kind, string requestId, string status, string details) + { + KuduEvent kuduEvent = new KuduEvent + { + siteName = siteName, + deploymentDetails = details, + deploymentStatus = status, + requestId = requestId, + }; + + LogKuduTraceEvent(kuduEvent); + } + public void WebJobStarted(string siteName, string jobName, string scriptExtension, string jobType, string siteMode, string error, string trigger) { KuduEvent kuduEvent = new KuduEvent diff --git a/Kudu.Services/Deployment/DeploymentController.cs b/Kudu.Services/Deployment/DeploymentController.cs index a468cc5c..65d158f8 100644 --- a/Kudu.Services/Deployment/DeploymentController.cs +++ b/Kudu.Services/Deployment/DeploymentController.cs @@ -3,8 +3,6 @@ using System.Globalization; using System.IO; using System.Linq; -using System.Net; -using System.Net.Http; using System.Threading.Tasks; using Kudu.Contracts.Infrastructure; using Kudu.Contracts.Settings; @@ -27,7 +25,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; -using Kudu.Services.Zip; using System.IO.Compression; namespace Kudu.Services.Deployment diff --git a/Kudu.Services/Deployment/PushDeploymentController.cs b/Kudu.Services/Deployment/PushDeploymentController.cs index 3c706527..58cf7dc8 100644 --- a/Kudu.Services/Deployment/PushDeploymentController.cs +++ b/Kudu.Services/Deployment/PushDeploymentController.cs @@ -31,7 +31,9 @@ namespace Kudu.Services.Deployment { public class PushDeploymentController : Controller { - private const string DefaultDeployer = "Push-Deployer"; + private const string ZipDeploy = "ZipDeploy"; + private const string ZipDeployUrl = "ZipDeploy-via-url"; + private const string WarDeploy = "WarDeploy"; private const string DefaultMessage = "Created via a push deployment"; private readonly IEnvironment _environment; @@ -64,7 +66,7 @@ public async Task ZipPushDeploy( [FromQuery] bool overwriteWebsiteRunFromPackage = false, [FromQuery] string author = null, [FromQuery] string authorEmail = null, - [FromQuery] string deployer = DefaultDeployer, + [FromQuery] string deployer = ZipDeploy, [FromQuery] string message = DefaultMessage) { using (_tracer.Step("ZipPushDeploy")) @@ -117,7 +119,7 @@ public async Task ZipPushDeployViaUrl( [FromQuery] bool overwriteWebsiteRunFromPackage = false, [FromQuery] string author = null, [FromQuery] string authorEmail = null, - [FromQuery] string deployer = DefaultDeployer, + [FromQuery] string deployer = ZipDeployUrl, [FromQuery] string message = DefaultMessage) { using (_tracer.Step("ZipPushDeployViaUrl")) @@ -158,7 +160,7 @@ public async Task WarPushDeploy( [FromQuery] bool isAsync = false, [FromQuery] string author = null, [FromQuery] string authorEmail = null, - [FromQuery] string deployer = DefaultDeployer, + [FromQuery] string deployer = WarDeploy, [FromQuery] string message = DefaultMessage) { using (_tracer.Step("WarPushDeploy"))