From 8ac0122e203d6e473900b1d66e44518a3105b71c Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 23 Oct 2020 15:52:40 +0100 Subject: [PATCH] Add migration to marker file & background reporter component to POST to telemetry service --- src/Umbraco.Core/IO/SystemFiles.cs | 2 + .../Migrations/Upgrade/UmbracoPlan.cs | 6 +- .../V_8_10_0/SetupAnonInstallTracker.cs | 49 ++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + src/Umbraco.Web/Telemetry/ReportSiteTask.cs | 109 ++++++++++++++++++ .../Telemetry/TelemetryComponent.cs | 35 ++++++ .../Telemetry/TelemetryComposer.cs | 10 ++ src/Umbraco.Web/Umbraco.Web.csproj | 3 + 8 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_8_10_0/SetupAnonInstallTracker.cs create mode 100644 src/Umbraco.Web/Telemetry/ReportSiteTask.cs create mode 100644 src/Umbraco.Web/Telemetry/TelemetryComponent.cs create mode 100644 src/Umbraco.Web/Telemetry/TelemetryComposer.cs diff --git a/src/Umbraco.Core/IO/SystemFiles.cs b/src/Umbraco.Core/IO/SystemFiles.cs index 12e3f57d99..5e38737dd2 100644 --- a/src/Umbraco.Core/IO/SystemFiles.cs +++ b/src/Umbraco.Core/IO/SystemFiles.cs @@ -7,6 +7,8 @@ namespace Umbraco.Core.IO { public static string TinyMceConfig => SystemDirectories.Config + "/tinyMceConfig.config"; + public static string TelemetricsIdentifier => SystemDirectories.Umbraco + "/telemetrics-id.umb"; + // TODO: Kill this off we don't have umbraco.config XML cache we now have NuCache public static string GetContentCacheXml(IGlobalSettings globalSettings) { diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index 723675ca1a..f97fcda49c 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -6,6 +6,7 @@ using Umbraco.Core.Migrations.Upgrade.Common; using Umbraco.Core.Migrations.Upgrade.V_8_0_0; using Umbraco.Core.Migrations.Upgrade.V_8_0_1; using Umbraco.Core.Migrations.Upgrade.V_8_1_0; +using Umbraco.Core.Migrations.Upgrade.V_8_10_0; using Umbraco.Core.Migrations.Upgrade.V_8_6_0; using Umbraco.Core.Migrations.Upgrade.V_8_8_0; @@ -197,7 +198,10 @@ namespace Umbraco.Core.Migrations.Upgrade // to 8.8.0 To("{B5838FF5-1D22-4F6C-BCEB-F83ACB14B575}"); - + + // to 8.10.0 + To("{DCBA2C6A-01B3-411E-9CDE-0AB9C69EFF33}"); + //FINAL } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_10_0/SetupAnonInstallTracker.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_10_0/SetupAnonInstallTracker.cs new file mode 100644 index 0000000000..6eded30312 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_10_0/SetupAnonInstallTracker.cs @@ -0,0 +1,49 @@ +using System; +using System.IO; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; + + +namespace Umbraco.Core.Migrations.Upgrade.V_8_10_0 +{ + + public class SetupAnonInstallTracker : MigrationBase + { + public SetupAnonInstallTracker(IMigrationContext context) + : base(context) + { + } + + /// + /// Adds a new file 'telemetrics-id.umb' at /umbraco + /// Which will add a GUID inside the file as JSON + /// + public override void Migrate() + { + var telemetricsFilePath = IOHelper.MapPath(SystemFiles.TelemetricsIdentifier); + + // Verify file does not exist already + if (File.Exists(telemetricsFilePath)) + { + Logger.Warn("When migrating to 8.10.0 the anonymous telemetry file already existsed on disk at {filePath}", telemetricsFilePath); + return; + } + + // Generate GUID + var telemetrySiteIdentifier = Guid.NewGuid(); + + // Write file contents + try + { + File.WriteAllText(telemetricsFilePath, telemetrySiteIdentifier.ToString()); + } + catch (Exception ex) + { + Logger.Error(ex, "Unable to create telemetry file at {filePath}", telemetricsFilePath); + } + + Logger.Info("This site has been identified with an anynomous id {telemetrySiteId} for telemetrics and written to {filePath}", telemetrySiteIdentifier, telemetricsFilePath); + + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 71b2a7be4a..821ddb3dc9 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -132,6 +132,7 @@ + diff --git a/src/Umbraco.Web/Telemetry/ReportSiteTask.cs b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs new file mode 100644 index 0000000000..8d777b73db --- /dev/null +++ b/src/Umbraco.Web/Telemetry/ReportSiteTask.cs @@ -0,0 +1,109 @@ +using System; +using System.IO; +using System.Net.Http; +using System.Net.Http.Formatting; +using System.Threading; +using System.Threading.Tasks; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Web.Scheduling; + +namespace Umbraco.Web.Telemetry +{ + public class ReportSiteTask : RecurringTaskBase + { + private IRuntimeState _runtime; + private IProfilingLogger _logger; + private static HttpClient _httpClient; + + public ReportSiteTask(IBackgroundTaskRunner runner, int delayBeforeWeStart, int howOftenWeRepeat, IRuntimeState runtime, IProfilingLogger logger) + : base(runner, delayBeforeWeStart, howOftenWeRepeat) + { + _runtime = runtime; + _logger = logger; + + if (_httpClient == null) + _httpClient = new HttpClient(); + } + + /// + /// Runs the background task to send the anynomous ID + /// to telemetry service + /// + /// A value indicating whether to repeat the task. + public override async Task PerformRunAsync(CancellationToken token) + { + // Try & find file at '/umbraco/telemetrics-id.umb' + var telemetricsFilePath = IOHelper.MapPath(SystemFiles.TelemetricsIdentifier); + + if (File.Exists(telemetricsFilePath) == false) + { + // Some users may have decided to not be tracked by deleting/removing the marker file + _logger.Warn("No telemetry marker file found at '{filePath}' and will not report site to telemetry service", telemetricsFilePath); + + // Stop repeating this task (no need to keep checking) + // The only time it will recheck when the site is recycled + return false; + } + + + var telemetricsFileContents = string.Empty; + try + { + // Open file & read its contents + // It may throw due to file permissions or file locking + telemetricsFileContents = File.ReadAllText(telemetricsFilePath); + } + catch (Exception ex) + { + // Silently swallow ex - but lets log it (ReadAllText throws a ton of different types of ex) + // Hence the use of general exception type + _logger.Error(ex, "Error in reading file contents of telemetry marker file found at '{filePath}'", telemetricsFilePath); + } + + + // Parse as a GUID & verify its a GUID and not some random string + // In case of users may have messed or decided to empty the file contents or put in something random + if (Guid.TryParse(telemetricsFileContents, out var telemetrySiteIdentifier) == false) + { + // Some users may have decided to mess with file contents + _logger.Warn("The telemetry marker file found at '{filePath}' with '{telemetrySiteId}' is not a valid identifier for the telemetry service", telemetricsFilePath, telemetrySiteIdentifier); + + // Stop repeating this task (no need to keep checking) + // The only time it will recheck when the site is recycled + return false; + } + + + try + { + // Make a HTTP Post to telemetry service + // https://telemetry.umbraco.com/installs + // Fire & Forget, do not need to know if its a 200, 500 etc + var postData = new TelemetryReportData { Id = telemetrySiteIdentifier, Version = UmbracoVersion.SemanticVersion.ToSemanticString() }; + var result = await _httpClient.PostAsync("https://webhook.site/9c38527a-eca4-4ad6-9847-202f2b37c07d", postData, new JsonMediaTypeFormatter()); + } + catch (Exception ex) + { + // Silently swallow + // The user does need logs being polluted if our service has fallen over or is down etc + } + + // Keep recurring this task & pinging the telemetry service + return true; + } + + public override bool IsAsync => true; + + + private class TelemetryReportData + { + public Guid Id { get; set; } + + public string Version { get; set; } + } + } +} + diff --git a/src/Umbraco.Web/Telemetry/TelemetryComponent.cs b/src/Umbraco.Web/Telemetry/TelemetryComponent.cs new file mode 100644 index 0000000000..c8113df58b --- /dev/null +++ b/src/Umbraco.Web/Telemetry/TelemetryComponent.cs @@ -0,0 +1,35 @@ +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; +using Umbraco.Web.Scheduling; + +namespace Umbraco.Web.Telemetry +{ + public class TelemetryComponent : IComponent + { + private IProfilingLogger _logger; + private IRuntimeState _runtime; + private BackgroundTaskRunner _telemetryReporterRunner; + + public TelemetryComponent(IProfilingLogger logger, IRuntimeState runtime) + { + _logger = logger; + _runtime = runtime; + _telemetryReporterRunner = new BackgroundTaskRunner("TelemetryReporter", _logger); + } + + public void Initialize() + { + int delayBeforeWeStart = 60 * 1000; // 60 * 1000ms = 1min (60,000) + int howOftenWeRepeat = 60 * 1000 * 60 * 24; // 60 * 1000 * 60 * 24 = 24hrs (86400000) + + // As soon as we add our task to the runner it will start to run (after its delay period) + var task = new ReportSiteTask(_telemetryReporterRunner, delayBeforeWeStart, howOftenWeRepeat, _runtime, _logger); + _telemetryReporterRunner.TryAdd(task); + } + + public void Terminate() + { + } + } +} diff --git a/src/Umbraco.Web/Telemetry/TelemetryComposer.cs b/src/Umbraco.Web/Telemetry/TelemetryComposer.cs new file mode 100644 index 0000000000..933b302e1c --- /dev/null +++ b/src/Umbraco.Web/Telemetry/TelemetryComposer.cs @@ -0,0 +1,10 @@ +using Umbraco.Core; +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Telemetry +{ + + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + public class TelemetryComposer : ComponentComposer, ICoreComposer + { } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 1b48b9ca0d..90088fe552 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -296,6 +296,9 @@ + + +