| @@ -9,6 +9,7 @@ using System.IO; | |||
| using System.Linq; | |||
| using System.Threading; | |||
| using System.Threading.Tasks; | |||
| using System.Runtime.InteropServices; | |||
| namespace Discord | |||
| { | |||
| @@ -24,11 +25,12 @@ namespace Discord | |||
| public event Func<Task> LoggedOut { add { _loggedOutEvent.Add(value); } remove { _loggedOutEvent.Remove(value); } } | |||
| private readonly AsyncEvent<Func<Task>> _loggedOutEvent = new AsyncEvent<Func<Task>>(); | |||
| internal readonly ILogger _discordLogger, _restLogger, _queueLogger; | |||
| internal readonly ILogger _clientLogger, _restLogger, _queueLogger; | |||
| internal readonly SemaphoreSlim _connectionLock; | |||
| internal readonly RequestQueue _requestQueue; | |||
| internal bool _isDisposed; | |||
| internal SelfUser _currentUser; | |||
| private bool _isFirstLogSub; | |||
| public API.DiscordApiClient ApiClient { get; } | |||
| internal LogManager LogManager { get; } | |||
| @@ -41,9 +43,10 @@ namespace Discord | |||
| { | |||
| LogManager = new LogManager(config.LogLevel); | |||
| LogManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false); | |||
| _discordLogger = LogManager.CreateLogger("Discord"); | |||
| _clientLogger = LogManager.CreateLogger("Client"); | |||
| _restLogger = LogManager.CreateLogger("Rest"); | |||
| _queueLogger = LogManager.CreateLogger("Queue"); | |||
| _isFirstLogSub = true; | |||
| _connectionLock = new SemaphoreSlim(1, 1); | |||
| @@ -73,6 +76,12 @@ namespace Discord | |||
| } | |||
| private async Task LoginInternalAsync(TokenType tokenType, string token, bool validateToken) | |||
| { | |||
| if (_isFirstLogSub) | |||
| { | |||
| _isFirstLogSub = false; | |||
| await WriteInitialLog().ConfigureAwait(false); | |||
| } | |||
| if (LoginState != LoginState.LoggedOut) | |||
| await LogoutInternalAsync().ConfigureAwait(false); | |||
| LoginState = LoginState.LoggingIn; | |||
| @@ -276,7 +285,28 @@ namespace Discord | |||
| } | |||
| /// <inheritdoc /> | |||
| public void Dispose() => Dispose(true); | |||
| protected async Task WriteInitialLog() | |||
| { | |||
| if (this is DiscordSocketClient) | |||
| await _clientLogger.InfoAsync($"DiscordSocketClient v{DiscordConfig.Version} (Gateway v{DiscordConfig.GatewayAPIVersion}, {DiscordConfig.GatewayEncoding})").ConfigureAwait(false); | |||
| else | |||
| await _clientLogger.InfoAsync($"DiscordClient v{DiscordConfig.Version}").ConfigureAwait(false); | |||
| await _clientLogger.VerboseAsync($"Runtime: {RuntimeInformation.FrameworkDescription.Trim()} ({ToArchString(RuntimeInformation.ProcessArchitecture)})").ConfigureAwait(false); | |||
| await _clientLogger.VerboseAsync($"OS: {RuntimeInformation.OSDescription.Trim()} ({ToArchString(RuntimeInformation.OSArchitecture)})").ConfigureAwait(false); | |||
| await _clientLogger.VerboseAsync($"Processors: {Environment.ProcessorCount}").ConfigureAwait(false); | |||
| } | |||
| private static string ToArchString(Architecture arch) | |||
| { | |||
| switch (arch) | |||
| { | |||
| case Architecture.X64: return "x64"; | |||
| case Architecture.X86: return "x86"; | |||
| default: return arch.ToString(); | |||
| } | |||
| } | |||
| ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; | |||
| ILogManager IDiscordClient.LogManager => LogManager; | |||
| @@ -450,7 +450,7 @@ namespace Discord | |||
| var data = (payload as JToken).ToObject<HelloEvent>(_serializer); | |||
| _heartbeatTime = 0; | |||
| _heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _cancelToken.Token); | |||
| _heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _cancelToken.Token, _clientLogger); | |||
| } | |||
| break; | |||
| case GatewayOpCode.Heartbeat: | |||
| @@ -526,7 +526,7 @@ namespace Discord | |||
| _lastGuildAvailableTime = Environment.TickCount; | |||
| DataStore = dataStore; | |||
| _guildDownloadTask = WaitForGuildsAsync(_cancelToken.Token); | |||
| _guildDownloadTask = WaitForGuildsAsync(_cancelToken.Token, _clientLogger); | |||
| await _readyEvent.InvokeAsync().ConfigureAwait(false); | |||
| await SyncGuildsAsync().ConfigureAwait(false); | |||
| @@ -1231,11 +1231,12 @@ namespace Discord | |||
| #endif | |||
| } | |||
| private async Task RunHeartbeatAsync(int intervalMillis, CancellationToken cancelToken) | |||
| private async Task RunHeartbeatAsync(int intervalMillis, CancellationToken cancelToken, ILogger logger) | |||
| { | |||
| //Clean this up when Discord's session patch is live | |||
| try | |||
| { | |||
| await logger.DebugAsync("Heartbeat Started").ConfigureAwait(false); | |||
| while (!cancelToken.IsCancellationRequested) | |||
| { | |||
| await Task.Delay(intervalMillis, cancelToken).ConfigureAwait(false); | |||
| @@ -1253,13 +1254,19 @@ namespace Discord | |||
| _heartbeatTime = Environment.TickCount; | |||
| await ApiClient.SendHeartbeatAsync(_lastSeq).ConfigureAwait(false); | |||
| } | |||
| await logger.DebugAsync("Heartbeat Stopped").ConfigureAwait(false); | |||
| } | |||
| catch (OperationCanceledException ex) | |||
| { | |||
| await logger.DebugAsync("Heartbeat Stopped", ex).ConfigureAwait(false); | |||
| } | |||
| catch (OperationCanceledException) { } | |||
| } | |||
| private async Task WaitForGuildsAsync(CancellationToken cancelToken) | |||
| private async Task WaitForGuildsAsync(CancellationToken cancelToken, ILogger logger) | |||
| { | |||
| await logger.DebugAsync("GuildDownloader Started").ConfigureAwait(false); | |||
| while ((_unavailableGuilds != 0) && (Environment.TickCount - _lastGuildAvailableTime < 2000)) | |||
| await Task.Delay(500, cancelToken).ConfigureAwait(false); | |||
| await logger.DebugAsync("GuildDownloader Stopped").ConfigureAwait(false); | |||
| } | |||
| private async Task SyncGuildsAsync() | |||
| { | |||
| @@ -20,26 +20,64 @@ namespace Discord | |||
| public override string ToString() => ToString(null, true); | |||
| public string ToString(StringBuilder builder = null, bool fullException = true) | |||
| public string ToString(StringBuilder builder = null, bool fullException = true, bool prependTimestamp = true, bool clearBuilder = true, DateTimeKind timestampKind = DateTimeKind.Local, int? padSource = 7) | |||
| { | |||
| string sourceName = Source; | |||
| string message = Message; | |||
| string exMessage = fullException ? Exception?.ToString() : Exception?.Message; | |||
| int maxLength = 1 + (sourceName?.Length ?? 0) + 2 + (message?.Length ?? 0) + 3 + (exMessage?.Length ?? 0); | |||
| int maxLength = 1 + | |||
| (prependTimestamp ? 8 : 0) + 1 + | |||
| (padSource.HasValue ? padSource.Value : sourceName?.Length ?? 0) + 1 + | |||
| (message?.Length ?? 0) + | |||
| (exMessage?.Length ?? 0) + 3; | |||
| if (builder == null) | |||
| builder = new StringBuilder(maxLength); | |||
| else | |||
| { | |||
| builder.Clear(); | |||
| builder.EnsureCapacity(maxLength); | |||
| if (clearBuilder) | |||
| { | |||
| builder.Clear(); | |||
| builder.EnsureCapacity(maxLength); | |||
| } | |||
| } | |||
| if (prependTimestamp) | |||
| { | |||
| DateTime now; | |||
| if (timestampKind == DateTimeKind.Utc) | |||
| now = DateTime.UtcNow; | |||
| else | |||
| now = DateTime.Now; | |||
| if (now.Hour < 10) | |||
| builder.Append('0'); | |||
| builder.Append(now.Hour); | |||
| builder.Append(':'); | |||
| if (now.Minute < 10) | |||
| builder.Append('0'); | |||
| builder.Append(now.Minute); | |||
| builder.Append(':'); | |||
| if (now.Second < 10) | |||
| builder.Append('0'); | |||
| builder.Append(now.Second); | |||
| builder.Append(' '); | |||
| } | |||
| if (sourceName != null) | |||
| { | |||
| builder.Append('['); | |||
| builder.Append(sourceName); | |||
| builder.Append("] "); | |||
| if (padSource.HasValue) | |||
| { | |||
| if (sourceName.Length < padSource.Value) | |||
| { | |||
| builder.Append(sourceName); | |||
| builder.Append(' ', padSource.Value - sourceName.Length); | |||
| } | |||
| else if (sourceName.Length > padSource.Value) | |||
| builder.Append(sourceName.Substring(0, padSource.Value)); | |||
| else | |||
| builder.Append(sourceName); | |||
| } | |||
| builder.Append(' '); | |||
| } | |||
| if (!string.IsNullOrEmpty(Message)) | |||
| { | |||
| @@ -53,7 +91,8 @@ namespace Discord | |||
| } | |||
| if (exMessage != null) | |||
| { | |||
| builder.AppendLine(":"); | |||
| builder.Append(':'); | |||
| builder.AppendLine(); | |||
| builder.Append(exMessage); | |||
| } | |||
| @@ -31,6 +31,7 @@ | |||
| "System.Net.WebSockets.Client": "4.0.0", | |||
| "System.Reflection.Extensions": "4.0.1", | |||
| "System.Runtime.InteropServices": "4.1.0", | |||
| "System.Runtime.InteropServices.RuntimeInformation": "4.0.0", | |||
| "System.Runtime.Serialization.Primitives": "4.1.1", | |||
| "System.Text.RegularExpressions": "4.1.0" | |||
| }, | |||