@@ -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" | ||||
}, | }, | ||||