// Copyright (C) 2015, The Duplicati Team // http://www.duplicati.com, info@duplicati.com // // This library is free software; you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation; either version 2.1 of the // License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA using System; using System.Threading.Tasks; using CoCoL; namespace Duplicati.Library.UsageReporter { /// /// The usage reporter library interface /// public static class Reporter { /// /// The tag used for logging /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(Reporter)); /// /// The primary input channel for new report messages /// private static IWriteChannel _eventChannel; /// /// The task to await before shutdown /// private static Task ShutdownTask; /// /// Reports an event, information by default /// /// The event name /// The event data /// The event type public static void Report(string key, string data = null, ReportType type = ReportType.Information) { if (_eventChannel != null && type >= MaxReportLevel) try { _eventChannel.WriteNoWait(new ReportItem(type, null, key, data)); } catch { } } /// /// Reports an event, information by default /// /// The event name /// The event count /// The event type public static void Report(string key, long count, ReportType type = ReportType.Information) { if (_eventChannel != null && type >= MaxReportLevel) try { _eventChannel.WriteNoWait(new ReportItem(type, count, key, count.ToString())); } catch { } } /// /// Reports an exception event, error by default /// /// The exception /// The event type public static void Report(Exception ex, ReportType type = ReportType.Warning) { if (_eventChannel != null && type >= MaxReportLevel) try { _eventChannel.WriteNoWait(new ReportItem(type, null, "EXCEPTION", ex.ToString())); } catch { } } /// /// Initializes the usage reporter library /// public static void Initialize() { if (_eventChannel == null || _eventChannel.IsRetiredAsync.Result) { if (IsDisabled) return; var rsu = ReportSetUploader.Run(); var ep = EventProcessor.Run(rsu.Item2); _eventChannel = ep.Item2; ShutdownTask = Task.WhenAll(ep.Item1, rsu.Item1); // TODO: Disable on debug builds AppDomain.CurrentDomain.UnhandledException += HandleUncaughtException; //AppDomain.CurrentDomain.UnhandledException += HandleUncaughtException; //AppDomain.CurrentDomain.ProcessExit Report("Started"); } } /// /// Handles an uncaught exception. /// /// Sender. /// Arguments. private static void HandleUncaughtException(object sender, UnhandledExceptionEventArgs args) { if (args.ExceptionObject is Exception exception) Report(exception, ReportType.Crash); } /// /// Terminates the usage reporter library /// public static void ShutDown() { if (_eventChannel != null && !_eventChannel.IsRetiredAsync.Result) _eventChannel.Retire(); if (ShutdownTask != null) { ShutdownTask.Wait(TimeSpan.FromSeconds(30)); if (!ShutdownTask.IsCompleted) Logging.Log.WriteWarningMessage(LOGTAG, "ReporterShutdownFailuer", null, "Failed to shut down usage reporter after 30 seconds, leaving hanging ..."); } AppDomain.CurrentDomain.UnhandledException -= HandleUncaughtException; } /// /// Allow opt-out /// internal const string DISABLED_ENVNAME_TEMPLATE = "USAGEREPORTER_{0}_LEVEL"; /// /// Cached value with the max report level /// private static ReportType? Cached_MaxReportLevel; /// /// A value indicating if the usage reporter library is forced disabled /// private static bool Forced_Disabled = false; /// /// Gets the environment default report level /// /// The system default report level. public static string DefaultReportLevel { get { return IsDisabledByEnvironment ? "Disabled" : MaxReportLevel.ToString(); } } /// /// The maximum allowed report level /// /// The type of the max report. private static ReportType MaxReportLevel { get { if (Cached_MaxReportLevel == null) { var str = Environment.GetEnvironmentVariable(string.Format(DISABLED_ENVNAME_TEMPLATE, AutoUpdater.AutoUpdateSettings.AppName)); ReportType tmp; if (string.IsNullOrWhiteSpace(str) || !Enum.TryParse(str, true, out tmp)) Cached_MaxReportLevel = ReportType.Information; else Cached_MaxReportLevel = tmp; } return Cached_MaxReportLevel.Value; } } /// /// Gets a value indicating if the user has opted out of usage reporting /// /// true if is disabled; otherwise, false. private static bool IsDisabled { get { if (Forced_Disabled) return true; return IsDisabledByEnvironment; } } /// /// Gets a value indicating if the user has opted out of usage reporting, /// but without reading the local override option /// /// true if is disabled; otherwise, false. private static bool IsDisabledByEnvironment { get { var str = Environment.GetEnvironmentVariable(string.Format(DISABLED_ENVNAME_TEMPLATE, AutoUpdater.AutoUpdateSettings.AppName)); #if DEBUG // Default to not report crashes etc from debug builds if (string.IsNullOrWhiteSpace(str)) str = "none"; #endif return string.Equals(str, "none", StringComparison.OrdinalIgnoreCase) || Utility.Utility.ParseBool(str, false); } } /// /// Sets the usage reporter level /// /// The maximum level of events to report, or null to set default. /// True to disable usage reportingfalse otherwise. public static void SetReportLevel(ReportType? maxreportlevel, bool disable) { if (disable) { Forced_Disabled = true; ShutDown(); } else { Forced_Disabled = false; Cached_MaxReportLevel = maxreportlevel; Initialize(); } } } }