Tracking failed imap setup steps for app insights.

This commit is contained in:
Burak Kaan Köse
2025-02-16 13:23:45 +01:00
parent 3ddc1a6229
commit f7836eedce
11 changed files with 422 additions and 331 deletions

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using SQLite; using SQLite;
using Wino.Core.Domain.Enums; using Wino.Core.Domain.Enums;
@@ -49,4 +50,25 @@ public class CustomServerInformation
/// Default is 5. /// Default is 5.
/// </summary> /// </summary>
public int MaxConcurrentClients { get; set; } public int MaxConcurrentClients { get; set; }
public Dictionary<string, string> GetConnectionProperties()
{
// Printout the public connection properties.
var connectionProperties = new Dictionary<string, string>
{
{ "IncomingServer", IncomingServer },
{ "IncomingServerPort", IncomingServerPort },
{ "IncomingServerSocketOption", IncomingServerSocketOption.ToString() },
{ "IncomingAuthenticationMethod", IncomingAuthenticationMethod.ToString() },
{ "OutgoingServer", OutgoingServer },
{ "OutgoingServerPort", OutgoingServerPort },
{ "OutgoingServerSocketOption", OutgoingServerSocketOption.ToString() },
{ "OutgoingAuthenticationMethod", OutgoingAuthenticationMethod.ToString() },
{ "ProxyServer", ProxyServer },
{ "ProxyServerPort", ProxyServerPort }
};
return connectionProperties;
}
} }

View File

@@ -1,13 +1,30 @@
using System; using System;
using Wino.Core.Domain.Entities.Shared;
namespace Wino.Core.Domain.Exceptions; namespace Wino.Core.Domain.Exceptions;
public class ImapClientPoolException : Exception public class ImapClientPoolException : Exception
{ {
public ImapClientPoolException()
{
}
public ImapClientPoolException(string message, CustomServerInformation customServerInformation, string protocolLog) : base(message)
{
CustomServerInformation = customServerInformation;
ProtocolLog = protocolLog;
}
public ImapClientPoolException(string message, string protocolLog) : base(message)
{
ProtocolLog = protocolLog;
}
public ImapClientPoolException(Exception innerException, string protocolLog) : base(Translator.Exception_ImapClientPoolFailed, innerException) public ImapClientPoolException(Exception innerException, string protocolLog) : base(Translator.Exception_ImapClientPoolFailed, innerException)
{ {
ProtocolLog = protocolLog; ProtocolLog = protocolLog;
} }
public CustomServerInformation CustomServerInformation { get; }
public string ProtocolLog { get; } public string ProtocolLog { get; }
} }

View File

@@ -1,8 +0,0 @@
namespace Wino.Core.Domain.Interfaces;
public interface ILogInitializer
{
void SetupLogger(string fullLogFilePath);
void RefreshLoggingLevel();
}

View File

@@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace Wino.Core.Domain.Interfaces;
public interface IWinoLogger
{
void SetupLogger(string fullLogFilePath);
void RefreshLoggingLevel();
void TrackEvent(string eventName, Dictionary<string, string> properties = null);
}

View File

@@ -22,15 +22,15 @@ using Wino.Core.Domain;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
using Wino.Services; using Wino.Services;
namespace Wino.Core.UWP; namespace Wino.Core.UWP
{
public abstract class WinoApplication : Application public abstract class WinoApplication : Application
{ {
public new static WinoApplication Current => (WinoApplication)Application.Current; public new static WinoApplication Current => (WinoApplication)Application.Current;
public const string WinoLaunchLogPrefix = "[Wino Launch] "; public const string WinoLaunchLogPrefix = "[Wino Launch] ";
public IServiceProvider Services { get; } public IServiceProvider Services { get; }
protected ILogInitializer LogInitializer { get; } protected IWinoLogger LogInitializer { get; }
protected IApplicationConfiguration AppConfiguration { get; } protected IApplicationConfiguration AppConfiguration { get; }
protected IWinoServerConnectionManager<AppServiceConnection> AppServiceConnectionManager { get; } protected IWinoServerConnectionManager<AppServiceConnection> AppServiceConnectionManager { get; }
protected IThemeService ThemeService { get; } protected IThemeService ThemeService { get; }
@@ -50,7 +50,7 @@ public abstract class WinoApplication : Application
Resuming += OnResuming; Resuming += OnResuming;
Suspending += OnSuspending; Suspending += OnSuspending;
LogInitializer = Services.GetService<ILogInitializer>(); LogInitializer = Services.GetService<IWinoLogger>();
AppConfiguration = Services.GetService<IApplicationConfiguration>(); AppConfiguration = Services.GetService<IApplicationConfiguration>();
AppServiceConnectionManager = Services.GetService<IWinoServerConnectionManager<AppServiceConnection>>(); AppServiceConnectionManager = Services.GetService<IWinoServerConnectionManager<AppServiceConnection>>();
@@ -246,3 +246,4 @@ public abstract class WinoApplication : Application
LogInitializer.SetupLogger(logFilePath); LogInitializer.SetupLogger(logFilePath);
} }
} }
}

View File

@@ -6,8 +6,8 @@ using Wino.Core.Domain;
using Wino.Core.Domain.Enums; using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
namespace Wino.Core.ViewModels; namespace Wino.Core.ViewModels
{
public partial class AboutPageViewModel : CoreBaseViewModel public partial class AboutPageViewModel : CoreBaseViewModel
{ {
private readonly IStoreRatingService _storeRatingService; private readonly IStoreRatingService _storeRatingService;
@@ -16,7 +16,7 @@ public partial class AboutPageViewModel : CoreBaseViewModel
private readonly IApplicationConfiguration _appInitializerService; private readonly IApplicationConfiguration _appInitializerService;
private readonly IClipboardService _clipboardService; private readonly IClipboardService _clipboardService;
private readonly IFileService _fileService; private readonly IFileService _fileService;
private readonly ILogInitializer _logInitializer; private readonly IWinoLogger _logInitializer;
public string VersionName => _nativeAppService.GetFullAppVersion(); public string VersionName => _nativeAppService.GetFullAppVersion();
public string DiscordChannelUrl => "https://discord.gg/windows-apps-hub-714581497222398064"; public string DiscordChannelUrl => "https://discord.gg/windows-apps-hub-714581497222398064";
@@ -33,7 +33,7 @@ public partial class AboutPageViewModel : CoreBaseViewModel
IApplicationConfiguration appInitializerService, IApplicationConfiguration appInitializerService,
IClipboardService clipboardService, IClipboardService clipboardService,
IFileService fileService, IFileService fileService,
ILogInitializer logInitializer) IWinoLogger logInitializer)
{ {
_storeRatingService = storeRatingService; _storeRatingService = storeRatingService;
_dialogService = dialogService; _dialogService = dialogService;
@@ -127,3 +127,4 @@ public partial class AboutPageViewModel : CoreBaseViewModel
private Task ShowRateDialogAsync() => _storeRatingService.LaunchStorePageForReviewAsync(); private Task ShowRateDialogAsync() => _storeRatingService.LaunchStorePageForReviewAsync();
} }
}

View File

@@ -1,5 +1,6 @@
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using MailKit.Net.Smtp;
using Wino.Core.Domain.Entities.Shared; using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Connectivity; using Wino.Core.Domain.Models.Connectivity;
@@ -53,5 +54,14 @@ public class ImapTestService : IImapTestService
clientPool.Release(client); clientPool.Release(client);
} }
// Test SMTP connectivity.
using var smtpClient = new SmtpClient();
if (!smtpClient.IsConnected)
await smtpClient.ConnectAsync(serverInformation.OutgoingServer, int.Parse(serverInformation.OutgoingServerPort), MailKit.Security.SecureSocketOptions.Auto);
if (!smtpClient.IsAuthenticated)
await smtpClient.AuthenticateAsync(serverInformation.OutgoingServerUsername, serverInformation.OutgoingServerPassword);
} }
} }

View File

@@ -13,6 +13,7 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Exceptions; using Wino.Core.Domain.Exceptions;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Authentication; using Wino.Core.Domain.Models.Authentication;
using Wino.Core.Domain.Models.Connectivity;
using Wino.Core.Domain.Models.Navigation; using Wino.Core.Domain.Models.Navigation;
using Wino.Core.Domain.Models.Synchronization; using Wino.Core.Domain.Models.Synchronization;
using Wino.Core.ViewModels; using Wino.Core.ViewModels;
@@ -28,6 +29,7 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
{ {
private readonly ISpecialImapProviderConfigResolver _specialImapProviderConfigResolver; private readonly ISpecialImapProviderConfigResolver _specialImapProviderConfigResolver;
private readonly IImapTestService _imapTestService; private readonly IImapTestService _imapTestService;
private readonly IWinoLogger _winoLogger;
public IMailDialogService MailDialogService { get; } public IMailDialogService MailDialogService { get; }
@@ -39,12 +41,14 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
IProviderService providerService, IProviderService providerService,
IImapTestService imapTestService, IImapTestService imapTestService,
IStoreManagementService storeManagementService, IStoreManagementService storeManagementService,
IWinoLogger winoLogger,
IAuthenticationProvider authenticationProvider, IAuthenticationProvider authenticationProvider,
IPreferencesService preferencesService) : base(dialogService, winoServerConnectionManager, navigationService, accountService, providerService, storeManagementService, authenticationProvider, preferencesService) IPreferencesService preferencesService) : base(dialogService, winoServerConnectionManager, navigationService, accountService, providerService, storeManagementService, authenticationProvider, preferencesService)
{ {
MailDialogService = dialogService; MailDialogService = dialogService;
_specialImapProviderConfigResolver = specialImapProviderConfigResolver; _specialImapProviderConfigResolver = specialImapProviderConfigResolver;
_imapTestService = imapTestService; _imapTestService = imapTestService;
_winoLogger = winoLogger;
} }
[RelayCommand] [RelayCommand]
@@ -111,6 +115,7 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
}; };
await creationDialog.ShowDialogAsync(accountCreationCancellationTokenSource); await creationDialog.ShowDialogAsync(accountCreationCancellationTokenSource);
creationDialog.State = AccountCreationDialogState.SigningIn; creationDialog.State = AccountCreationDialogState.SigningIn;
string tokenInformation = string.Empty; string tokenInformation = string.Empty;
@@ -146,7 +151,18 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
createdAccount.SenderName = accountCreationDialogResult.SpecialImapProviderDetails.SenderName; createdAccount.SenderName = accountCreationDialogResult.SpecialImapProviderDetails.SenderName;
createdAccount.Address = customServerInformation.Address; createdAccount.Address = customServerInformation.Address;
await _imapTestService.TestImapConnectionAsync(customServerInformation, true); // Let server validate the imap/smtp connection.
var testResultResponse = await WinoServerConnectionManager.GetResponseAsync<ImapConnectivityTestResults, ImapConnectivityTestRequested>(new ImapConnectivityTestRequested(customServerInformation, true));
if (!testResultResponse.IsSuccess)
{
throw new Exception($"{Translator.IMAPSetupDialog_ConnectionFailedTitle}\n{testResultResponse.Message}");
}
else if (!testResultResponse.Data.IsSuccess)
{
// Server connectivity might succeed, but result might be failed.
throw new ImapClientPoolException(testResultResponse.Data.FailedReason, customServerInformation, testResultResponse.Data.FailureProtocolLog);
}
} }
else else
{ {
@@ -266,7 +282,18 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
{ {
// Ignore // Ignore
} }
catch (ImapClientPoolException clientPoolException) catch (ImapClientPoolException testClientPoolException) when (testClientPoolException.CustomServerInformation != null)
{
var properties = testClientPoolException.CustomServerInformation.GetConnectionProperties();
properties.Add("ProtocolLog", testClientPoolException.ProtocolLog);
properties.Add("DiagnosticId", PreferencesService.DiagnosticId);
_winoLogger.TrackEvent("IMAP Test Failed", properties);
DialogService.InfoBarMessage(Translator.Info_AccountCreationFailedTitle, testClientPoolException.Message, InfoBarMessageType.Error);
}
catch (ImapClientPoolException clientPoolException) when (clientPoolException.InnerException != null)
{ {
DialogService.InfoBarMessage(Translator.Info_AccountCreationFailedTitle, clientPoolException.InnerException.Message, InfoBarMessageType.Error); DialogService.InfoBarMessage(Translator.Info_AccountCreationFailedTitle, clientPoolException.InnerException.Message, InfoBarMessageType.Error);
} }

View File

@@ -111,7 +111,7 @@ public partial class App : Application
applicationFolderConfiguration.ApplicationTempFolderPath = ApplicationData.Current.TemporaryFolder.Path; applicationFolderConfiguration.ApplicationTempFolderPath = ApplicationData.Current.TemporaryFolder.Path;
// Setup logger // Setup logger
var logInitializer = Services.GetService<ILogInitializer>(); var logInitializer = Services.GetService<IWinoLogger>();
var logFilePath = Path.Combine(ApplicationData.Current.LocalFolder.Path, Constants.ServerLogFile); var logFilePath = Path.Combine(ApplicationData.Current.LocalFolder.Path, Constants.ServerLogFile);
logInitializer.SetupLogger(logFilePath); logInitializer.SetupLogger(logFilePath);

View File

@@ -2,8 +2,8 @@
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
using Wino.Services.Threading; using Wino.Services.Threading;
namespace Wino.Services; namespace Wino.Services
{
public static class ServicesContainerSetup public static class ServicesContainerSetup
{ {
public static void RegisterSharedServices(this IServiceCollection services) public static void RegisterSharedServices(this IServiceCollection services)
@@ -12,7 +12,7 @@ public static class ServicesContainerSetup
services.AddSingleton<IDatabaseService, DatabaseService>(); services.AddSingleton<IDatabaseService, DatabaseService>();
services.AddSingleton<IApplicationConfiguration, ApplicationConfiguration>(); services.AddSingleton<IApplicationConfiguration, ApplicationConfiguration>();
services.AddSingleton<ILogInitializer, LogInitializer>(); services.AddSingleton<IWinoLogger, WinoLogger>();
services.AddSingleton<ILaunchProtocolService, LaunchProtocolService>(); services.AddSingleton<ILaunchProtocolService, LaunchProtocolService>();
services.AddSingleton<IMimeFileService, MimeFileService>(); services.AddSingleton<IMimeFileService, MimeFileService>();
@@ -33,3 +33,4 @@ public static class ServicesContainerSetup
} }
} }
}

View File

@@ -1,4 +1,6 @@
using Microsoft.ApplicationInsights.Extensibility; using System.Collections.Generic;
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.Extensibility;
using Serilog; using Serilog;
using Serilog.Core; using Serilog.Core;
using Serilog.Exceptions; using Serilog.Exceptions;
@@ -7,20 +9,21 @@ using Wino.Services.Misc;
namespace Wino.Services; namespace Wino.Services;
public class LogInitializer : ILogInitializer public class WinoLogger : IWinoLogger
{ {
private readonly LoggingLevelSwitch _levelSwitch = new LoggingLevelSwitch(); private readonly LoggingLevelSwitch _levelSwitch = new LoggingLevelSwitch();
private readonly IPreferencesService _preferencesService; private readonly IPreferencesService _preferencesService;
private readonly IApplicationConfiguration _applicationConfiguration;
private readonly TelemetryConfiguration _telemetryConfiguration; private readonly TelemetryConfiguration _telemetryConfiguration;
public LogInitializer(IPreferencesService preferencesService, IApplicationConfiguration applicationConfiguration) public TelemetryClient TelemetryClient { get; private set; }
public WinoLogger(IPreferencesService preferencesService, IApplicationConfiguration applicationConfiguration)
{ {
_preferencesService = preferencesService; _preferencesService = preferencesService;
_applicationConfiguration = applicationConfiguration;
_telemetryConfiguration = new TelemetryConfiguration(applicationConfiguration.ApplicationInsightsInstrumentationKey); _telemetryConfiguration = new TelemetryConfiguration(applicationConfiguration.ApplicationInsightsInstrumentationKey);
TelemetryClient = new TelemetryClient(_telemetryConfiguration);
RefreshLoggingLevel(); RefreshLoggingLevel();
} }
@@ -41,9 +44,16 @@ public class LogInitializer : ILogInitializer
.MinimumLevel.ControlledBy(_levelSwitch) .MinimumLevel.ControlledBy(_levelSwitch)
.WriteTo.File(fullLogFilePath, retainedFileCountLimit: 3, rollOnFileSizeLimit: true, rollingInterval: RollingInterval.Day) .WriteTo.File(fullLogFilePath, retainedFileCountLimit: 3, rollOnFileSizeLimit: true, rollingInterval: RollingInterval.Day)
.WriteTo.Debug() .WriteTo.Debug()
.WriteTo.ApplicationInsights(_telemetryConfiguration, insightsTelemetryConverter, restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Error) .WriteTo.ApplicationInsights(TelemetryClient, insightsTelemetryConverter, restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Error)
.Enrich.FromLogContext() .Enrich.FromLogContext()
.Enrich.WithExceptionDetails() .Enrich.WithExceptionDetails()
.CreateLogger(); .CreateLogger();
} }
public void TrackEvent(string eventName, Dictionary<string, string> properties = null)
{
if (TelemetryClient == null) return;
TelemetryClient.TrackEvent(eventName, properties);
}
} }