Merge pull request #9370 from umbraco/v8/feature/anon-telemetry

Anonymous telemetry
This commit is contained in:
Warren Buckley
2020-11-30 13:23:12 +00:00
committed by GitHub
9 changed files with 254 additions and 1 deletions

1
.gitignore vendored
View File

@@ -175,3 +175,4 @@ cypress.env.json
/src/Umbraco.Tests.AcceptanceTest/package-lock.json
/src/Umbraco.Tests.AcceptanceTest/cypress/videos/
/src/Umbraco.Tests.AcceptanceTest/cypress/screenshots/
src/Umbraco.Web.UI/Umbraco/telemetrics-id.umb

View File

@@ -7,6 +7,8 @@ namespace Umbraco.Core.IO
{
public static string TinyMceConfig => SystemDirectories.Config + "/tinyMceConfig.config";
public static string TelemetricsIdentifier => SystemDirectories.Data + "/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)
{

View File

@@ -199,7 +199,7 @@ namespace Umbraco.Core.Migrations.Upgrade
// to 8.9.0
To<ExternalLoginTableUserData>("{B5838FF5-1D22-4F6C-BCEB-F83ACB14B575}");
// to 8.10.0...
// to 8.10.0
To<AddPropertyTypeLabelOnTopColumn>("{D6A8D863-38EC-44FB-91EC-ACD6A668BD18}");
//FINAL

View File

@@ -0,0 +1,132 @@
using Newtonsoft.Json;
using System;
using System.IO;
using System.Net.Http;
using System.Runtime.Serialization;
using System.Text;
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 IProfilingLogger _logger;
private static HttpClient _httpClient;
public ReportSiteTask(IBackgroundTaskRunner<RecurringTaskBase> runner, int delayBeforeWeStart, int howOftenWeRepeat, IProfilingLogger logger)
: base(runner, delayBeforeWeStart, howOftenWeRepeat)
{
_logger = logger;
_httpClient = new HttpClient();
}
/// <summary>
/// Runs the background task to send the anynomous ID
/// to telemetry service
/// </summary>
/// <returns>A value indicating whether to repeat the task.</returns>
public override async Task<bool> 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<ReportSiteTask>("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<ReportSiteTask>(ex, "Error in reading file contents of telemetry marker file found at '{filePath}'", telemetricsFilePath);
// Exit out early, but mark this task to be repeated in case its a file lock so it can be rechecked the next time round
return true;
}
// 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<ReportSiteTask>("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
{
// Send data to LIVE telemetry
_httpClient.BaseAddress = new Uri("https://telemetry.umbraco.com/");
#if DEBUG
// Send data to DEBUG telemetry service
_httpClient.BaseAddress = new Uri("https://telemetry.rainbowsrock.net/");
#endif
_httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json");
using (var request = new HttpRequestMessage(HttpMethod.Post, "installs/"))
{
var postData = new TelemetryReportData { Id = telemetrySiteIdentifier, Version = UmbracoVersion.SemanticVersion.ToSemanticString() };
request.Content = new StringContent(JsonConvert.SerializeObject(postData), Encoding.UTF8, "application/json"); //CONTENT-TYPE header
// Set a low timeout - no need to use a larger default timeout for this POST request
_httpClient.Timeout = new TimeSpan(0, 0, 1);
// 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 result = await _httpClient.SendAsync(request);
}
}
catch
{
// Silently swallow
// The user does not need the logs being polluted if our service has fallen over or is down etc
// Hence only loggigng this at a more verbose level (Which users should not be using in prod)
_logger.Debug<ReportSiteTask>("There was a problem sending a request to the Umbraco telemetry service");
}
// Keep recurring this task & pinging the telemetry service
return true;
}
public override bool IsAsync => true;
[DataContract]
private class TelemetryReportData
{
[DataMember(Name = "id")]
public Guid Id { get; set; }
[DataMember(Name = "version")]
public string Version { get; set; }
}
}
}

View File

@@ -0,0 +1,34 @@
using Umbraco.Core.Composing;
using Umbraco.Core.Logging;
using Umbraco.Web.Scheduling;
namespace Umbraco.Web.Telemetry
{
public class TelemetryComponent : IComponent
{
private IProfilingLogger _logger;
private BackgroundTaskRunner<IBackgroundTask> _telemetryReporterRunner;
public TelemetryComponent(IProfilingLogger logger)
{
_logger = logger;
}
public void Initialize()
{
// backgrounds runners are web aware, if the app domain dies, these tasks will wind down correctly
_telemetryReporterRunner = new BackgroundTaskRunner<IBackgroundTask>("TelemetryReporter", _logger);
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, _logger);
_telemetryReporterRunner.TryAdd(task);
}
public void Terminate()
{
}
}
}

View File

@@ -0,0 +1,10 @@
using Umbraco.Core;
using Umbraco.Core.Composing;
namespace Umbraco.Web.Telemetry
{
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
public class TelemetryComposer : ComponentComposer<TelemetryComponent>, ICoreComposer
{ }
}

View File

@@ -0,0 +1,60 @@
using System;
using System.IO;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
namespace Umbraco.Web.Telemetry
{
public class TelemetryMarkerComponent : IComponent
{
private IProfilingLogger _logger;
private IRuntimeState _runtime;
public TelemetryMarkerComponent(IProfilingLogger logger, IRuntimeState runtime)
{
_logger = logger;
_runtime = runtime;
}
public void Initialize()
{
var telemetricsFilePath = IOHelper.MapPath(SystemFiles.TelemetricsIdentifier);
// Verify file does not exist already (if we are upgrading)
// In a clean install we know it would not exist
// If the site is upgraded and the file was removed it would re-create one
// NOTE: If user removed the marker file to opt out it would re-create a new guid marker file & potentially skew
if (_runtime.Level == RuntimeLevel.Upgrade && File.Exists(telemetricsFilePath))
{
_logger.Warn<TelemetryMarkerComponent>("When upgrading the anonymous telemetry file already existsed on disk at {filePath}", telemetricsFilePath);
return;
}
else if (_runtime.Level == RuntimeLevel.Install && File.Exists(telemetricsFilePath))
{
// No need to log for when level is install if file exists (As this component hit several times during install process)
return;
}
// We are a clean install or an upgrade without the marker file
// Generate GUID
var telemetrySiteIdentifier = Guid.NewGuid();
// Write file contents
try
{
File.WriteAllText(telemetricsFilePath, telemetrySiteIdentifier.ToString());
}
catch (Exception ex)
{
_logger.Error<TelemetryMarkerComponent>(ex, "Unable to create telemetry file at {filePath}", telemetricsFilePath);
}
}
public void Terminate()
{
}
}
}

View File

@@ -0,0 +1,9 @@
using Umbraco.Core;
using Umbraco.Core.Composing;
namespace Umbraco.Web.Telemetry
{
[RuntimeLevel(MinLevel = RuntimeLevel.Install, MaxLevel = RuntimeLevel.Upgrade)]
public class TelemetryMarkerComposer : ComponentComposer<TelemetryMarkerComponent>, ICoreComposer
{ }
}

View File

@@ -295,6 +295,11 @@
<Compile Include="Models\Link.cs" />
<Compile Include="Models\LinkType.cs" />
<Compile Include="Models\TemplateQuery\OperatorFactory.cs" />
<Compile Include="Telemetry\ReportSiteTask.cs" />
<Compile Include="Telemetry\TelemetryMarkerComponent.cs" />
<Compile Include="Telemetry\TelemetryComponent.cs" />
<Compile Include="Telemetry\TelemetryComposer.cs" />
<Compile Include="Telemetry\TelemetryMarkerComposer.cs" />
<Compile Include="Templates\HtmlLocalLinkParser.cs" />
<Compile Include="Templates\HtmlImageSourceParser.cs" />
<Compile Include="Templates\HtmlUrlParser.cs" />