| @@ -9,6 +9,7 @@ using System.IO; | |||||
| using System.Linq; | using System.Linq; | ||||
| using System.Threading; | using System.Threading; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using System.Runtime.InteropServices; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| @@ -24,11 +25,12 @@ namespace Discord | |||||
| public event Func<Task> LoggedOut { add { _loggedOutEvent.Add(value); } remove { _loggedOutEvent.Remove(value); } } | public event Func<Task> LoggedOut { add { _loggedOutEvent.Add(value); } remove { _loggedOutEvent.Remove(value); } } | ||||
| private readonly AsyncEvent<Func<Task>> _loggedOutEvent = new AsyncEvent<Func<Task>>(); | 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 SemaphoreSlim _connectionLock; | ||||
| internal readonly RequestQueue _requestQueue; | internal readonly RequestQueue _requestQueue; | ||||
| internal bool _isDisposed; | internal bool _isDisposed; | ||||
| internal SelfUser _currentUser; | internal SelfUser _currentUser; | ||||
| private bool _isFirstLogSub; | |||||
| public API.DiscordApiClient ApiClient { get; } | public API.DiscordApiClient ApiClient { get; } | ||||
| internal LogManager LogManager { get; } | internal LogManager LogManager { get; } | ||||
| @@ -41,9 +43,10 @@ namespace Discord | |||||
| { | { | ||||
| LogManager = new LogManager(config.LogLevel); | LogManager = new LogManager(config.LogLevel); | ||||
| LogManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false); | LogManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false); | ||||
| _discordLogger = LogManager.CreateLogger("Discord"); | |||||
| _clientLogger = LogManager.CreateLogger("Client"); | |||||
| _restLogger = LogManager.CreateLogger("Rest"); | _restLogger = LogManager.CreateLogger("Rest"); | ||||
| _queueLogger = LogManager.CreateLogger("Queue"); | _queueLogger = LogManager.CreateLogger("Queue"); | ||||
| _isFirstLogSub = true; | |||||
| _connectionLock = new SemaphoreSlim(1, 1); | _connectionLock = new SemaphoreSlim(1, 1); | ||||
| @@ -73,6 +76,12 @@ namespace Discord | |||||
| } | } | ||||
| private async Task LoginInternalAsync(TokenType tokenType, string token, bool validateToken) | private async Task LoginInternalAsync(TokenType tokenType, string token, bool validateToken) | ||||
| { | { | ||||
| if (_isFirstLogSub) | |||||
| { | |||||
| _isFirstLogSub = false; | |||||
| await WriteInitialLog().ConfigureAwait(false); | |||||
| } | |||||
| if (LoginState != LoginState.LoggedOut) | if (LoginState != LoginState.LoggedOut) | ||||
| await LogoutInternalAsync().ConfigureAwait(false); | await LogoutInternalAsync().ConfigureAwait(false); | ||||
| LoginState = LoginState.LoggingIn; | LoginState = LoginState.LoggingIn; | ||||
| @@ -276,7 +285,28 @@ namespace Discord | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public void Dispose() => Dispose(true); | 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; | ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; | ||||
| ILogManager IDiscordClient.LogManager => LogManager; | ILogManager IDiscordClient.LogManager => LogManager; | ||||
| @@ -450,7 +450,7 @@ namespace Discord | |||||
| var data = (payload as JToken).ToObject<HelloEvent>(_serializer); | var data = (payload as JToken).ToObject<HelloEvent>(_serializer); | ||||
| _heartbeatTime = 0; | _heartbeatTime = 0; | ||||
| _heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _cancelToken.Token); | |||||
| _heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _cancelToken.Token, _clientLogger); | |||||
| } | } | ||||
| break; | break; | ||||
| case GatewayOpCode.Heartbeat: | case GatewayOpCode.Heartbeat: | ||||
| @@ -526,7 +526,7 @@ namespace Discord | |||||
| _lastGuildAvailableTime = Environment.TickCount; | _lastGuildAvailableTime = Environment.TickCount; | ||||
| DataStore = dataStore; | DataStore = dataStore; | ||||
| _guildDownloadTask = WaitForGuildsAsync(_cancelToken.Token); | |||||
| _guildDownloadTask = WaitForGuildsAsync(_cancelToken.Token, _clientLogger); | |||||
| await _readyEvent.InvokeAsync().ConfigureAwait(false); | await _readyEvent.InvokeAsync().ConfigureAwait(false); | ||||
| await SyncGuildsAsync().ConfigureAwait(false); | await SyncGuildsAsync().ConfigureAwait(false); | ||||
| @@ -1231,11 +1231,12 @@ namespace Discord | |||||
| #endif | #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 | //Clean this up when Discord's session patch is live | ||||
| try | try | ||||
| { | { | ||||
| await logger.DebugAsync("Heartbeat Started").ConfigureAwait(false); | |||||
| while (!cancelToken.IsCancellationRequested) | while (!cancelToken.IsCancellationRequested) | ||||
| { | { | ||||
| await Task.Delay(intervalMillis, cancelToken).ConfigureAwait(false); | await Task.Delay(intervalMillis, cancelToken).ConfigureAwait(false); | ||||
| @@ -1253,13 +1254,19 @@ namespace Discord | |||||
| _heartbeatTime = Environment.TickCount; | _heartbeatTime = Environment.TickCount; | ||||
| await ApiClient.SendHeartbeatAsync(_lastSeq).ConfigureAwait(false); | 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)) | while ((_unavailableGuilds != 0) && (Environment.TickCount - _lastGuildAvailableTime < 2000)) | ||||
| await Task.Delay(500, cancelToken).ConfigureAwait(false); | await Task.Delay(500, cancelToken).ConfigureAwait(false); | ||||
| await logger.DebugAsync("GuildDownloader Stopped").ConfigureAwait(false); | |||||
| } | } | ||||
| private async Task SyncGuildsAsync() | private async Task SyncGuildsAsync() | ||||
| { | { | ||||
| @@ -20,26 +20,64 @@ namespace Discord | |||||
| public override string ToString() => ToString(null, true); | 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 sourceName = Source; | ||||
| string message = Message; | string message = Message; | ||||
| string exMessage = fullException ? Exception?.ToString() : Exception?.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) | if (builder == null) | ||||
| builder = new StringBuilder(maxLength); | builder = new StringBuilder(maxLength); | ||||
| else | 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) | 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)) | if (!string.IsNullOrEmpty(Message)) | ||||
| { | { | ||||
| @@ -53,7 +91,8 @@ namespace Discord | |||||
| } | } | ||||
| if (exMessage != null) | if (exMessage != null) | ||||
| { | { | ||||
| builder.AppendLine(":"); | |||||
| builder.Append(':'); | |||||
| builder.AppendLine(); | |||||
| builder.Append(exMessage); | builder.Append(exMessage); | ||||
| } | } | ||||
| @@ -31,6 +31,7 @@ | |||||
| "System.Net.WebSockets.Client": "4.0.0", | "System.Net.WebSockets.Client": "4.0.0", | ||||
| "System.Reflection.Extensions": "4.0.1", | "System.Reflection.Extensions": "4.0.1", | ||||
| "System.Runtime.InteropServices": "4.1.0", | "System.Runtime.InteropServices": "4.1.0", | ||||
| "System.Runtime.InteropServices.RuntimeInformation": "4.0.0", | |||||
| "System.Runtime.Serialization.Primitives": "4.1.1", | "System.Runtime.Serialization.Primitives": "4.1.1", | ||||
| "System.Text.RegularExpressions": "4.1.0" | "System.Text.RegularExpressions": "4.1.0" | ||||
| }, | }, | ||||