| @@ -31,8 +31,7 @@ namespace _01_basic_ping_bot | |||||
| // It is recommended to Dispose of a client when you are finished | // It is recommended to Dispose of a client when you are finished | ||||
| // using it, at the end of your app's lifetime. | // using it, at the end of your app's lifetime. | ||||
| _client = new DiscordSocketClient(); | _client = new DiscordSocketClient(); | ||||
| _client.Log += LogAsync; | |||||
| _client.Ready += ReadyAsync; | _client.Ready += ReadyAsync; | ||||
| _client.MessageReceived += MessageReceivedAsync; | _client.MessageReceived += MessageReceivedAsync; | ||||
| } | } | ||||
| @@ -47,12 +46,6 @@ namespace _01_basic_ping_bot | |||||
| await Task.Delay(Timeout.Infinite); | await Task.Delay(Timeout.Infinite); | ||||
| } | } | ||||
| private Task LogAsync(LogMessage log) | |||||
| { | |||||
| Console.WriteLine(log.ToString()); | |||||
| return Task.CompletedTask; | |||||
| } | |||||
| // The Ready event indicates that the client has opened a | // The Ready event indicates that the client has opened a | ||||
| // connection and it is now safe to access the cache. | // connection and it is now safe to access the cache. | ||||
| private Task ReadyAsync() | private Task ReadyAsync() | ||||
| @@ -35,9 +35,6 @@ namespace _02_commands_framework | |||||
| { | { | ||||
| var client = services.GetRequiredService<DiscordSocketClient>(); | var client = services.GetRequiredService<DiscordSocketClient>(); | ||||
| client.Log += LogAsync; | |||||
| services.GetRequiredService<CommandService>().Log += LogAsync; | |||||
| // Tokens should be considered secret data and never hard-coded. | // Tokens should be considered secret data and never hard-coded. | ||||
| // We can read from the environment variable to avoid hardcoding. | // We can read from the environment variable to avoid hardcoding. | ||||
| await client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token")); | await client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token")); | ||||
| @@ -50,13 +47,6 @@ namespace _02_commands_framework | |||||
| } | } | ||||
| } | } | ||||
| private Task LogAsync(LogMessage log) | |||||
| { | |||||
| Console.WriteLine(log.ToString()); | |||||
| return Task.CompletedTask; | |||||
| } | |||||
| private ServiceProvider ConfigureServices() | private ServiceProvider ConfigureServices() | ||||
| { | { | ||||
| return new ServiceCollection() | return new ServiceCollection() | ||||
| @@ -38,7 +38,6 @@ namespace _03_sharded_client | |||||
| // The ShardReady event is used instead, allowing for individual | // The ShardReady event is used instead, allowing for individual | ||||
| // control per shard. | // control per shard. | ||||
| client.ShardReady += ReadyAsync; | client.ShardReady += ReadyAsync; | ||||
| client.Log += LogAsync; | |||||
| await services.GetRequiredService<CommandHandlingService>().InitializeAsync(); | await services.GetRequiredService<CommandHandlingService>().InitializeAsync(); | ||||
| @@ -65,11 +64,5 @@ namespace _03_sharded_client | |||||
| Console.WriteLine($"Shard Number {shard.ShardId} is connected and ready!"); | Console.WriteLine($"Shard Number {shard.ShardId} is connected and ready!"); | ||||
| return Task.CompletedTask; | return Task.CompletedTask; | ||||
| } | } | ||||
| private Task LogAsync(LogMessage log) | |||||
| { | |||||
| Console.WriteLine(log.ToString()); | |||||
| return Task.CompletedTask; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -21,7 +21,6 @@ namespace _03_sharded_client.Services | |||||
| _services = services; | _services = services; | ||||
| _commands.CommandExecuted += CommandExecutedAsync; | _commands.CommandExecuted += CommandExecutedAsync; | ||||
| _commands.Log += LogAsync; | |||||
| _discord.MessageReceived += MessageReceivedAsync; | _discord.MessageReceived += MessageReceivedAsync; | ||||
| } | } | ||||
| @@ -61,12 +60,5 @@ namespace _03_sharded_client.Services | |||||
| // the command failed, let's notify the user that something happened. | // the command failed, let's notify the user that something happened. | ||||
| await context.Channel.SendMessageAsync($"error: {result.ToString()}"); | await context.Channel.SendMessageAsync($"error: {result.ToString()}"); | ||||
| } | } | ||||
| private Task LogAsync(LogMessage log) | |||||
| { | |||||
| Console.WriteLine(log.ToString()); | |||||
| return Task.CompletedTask; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -34,60 +34,15 @@ namespace idn | |||||
| static async Task Main(string[] args) | static async Task Main(string[] args) | ||||
| { | { | ||||
| var token = File.ReadAllText("token.ignore"); | var token = File.ReadAllText("token.ignore"); | ||||
| var client = new DiscordSocketClient(new DiscordSocketConfig { LogLevel = LogSeverity.Debug }); | |||||
| var logQueue = new ConcurrentQueue<LogMessage>(); | |||||
| var client = new DiscordSocketClient(new DiscordSocketConfig { }); | |||||
| var logCancelToken = new CancellationTokenSource(); | var logCancelToken = new CancellationTokenSource(); | ||||
| int presenceUpdates = 0; | int presenceUpdates = 0; | ||||
| client.Log += msg => | |||||
| { | |||||
| logQueue.Enqueue(msg); | |||||
| return Task.CompletedTask; | |||||
| }; | |||||
| Console.CancelKeyPress += (_ev, _s) => | Console.CancelKeyPress += (_ev, _s) => | ||||
| { | { | ||||
| logCancelToken.Cancel(); | logCancelToken.Cancel(); | ||||
| }; | }; | ||||
| var logTask = Task.Run(async () => | |||||
| { | |||||
| var fs = new FileStream("idn.log", FileMode.Append); | |||||
| var logStringBuilder = new StringBuilder(200); | |||||
| string logString = ""; | |||||
| byte[] helloBytes = Encoding.UTF8.GetBytes($"### new log session: {DateTime.Now} ###\n\n"); | |||||
| await fs.WriteAsync(helloBytes); | |||||
| while (!logCancelToken.IsCancellationRequested) | |||||
| { | |||||
| if (logQueue.TryDequeue(out var msg)) | |||||
| { | |||||
| if (msg.Message?.IndexOf("PRESENCE_UPDATE)") > 0) | |||||
| { | |||||
| presenceUpdates++; | |||||
| continue; | |||||
| } | |||||
| _ = msg.ToString(builder: logStringBuilder); | |||||
| logStringBuilder.AppendLine(); | |||||
| logString = logStringBuilder.ToString(); | |||||
| Debug.Write(logString, "DNET"); | |||||
| await fs.WriteAsync(Encoding.UTF8.GetBytes(logString)); | |||||
| } | |||||
| await fs.FlushAsync(); | |||||
| try | |||||
| { | |||||
| await Task.Delay(100, logCancelToken.Token); | |||||
| } | |||||
| finally { } | |||||
| } | |||||
| byte[] goodbyeBytes = Encoding.UTF8.GetBytes($"#!! end log session: {DateTime.Now} !!#\n\n\n"); | |||||
| await fs.WriteAsync(goodbyeBytes); | |||||
| await fs.DisposeAsync(); | |||||
| }); | |||||
| await client.LoginAsync(TokenType.Bot, token); | await client.LoginAsync(TokenType.Bot, token); | ||||
| await client.StartAsync(); | await client.StartAsync(); | ||||
| @@ -127,9 +82,9 @@ namespace idn | |||||
| await client.StopAsync(); | await client.StopAsync(); | ||||
| client.Dispose(); | client.Dispose(); | ||||
| logCancelToken.Cancel(); | logCancelToken.Cancel(); | ||||
| try | |||||
| { await logTask; } | |||||
| finally { Console.WriteLine("goodbye!"); } | |||||
| await Task.Delay(-1, logCancelToken.Token); | |||||
| Console.WriteLine("goodbye!"); | |||||
| } | } | ||||
| static IEnumerable<Assembly> GetAssemblies() | static IEnumerable<Assembly> GetAssemblies() | ||||
| @@ -5,6 +5,7 @@ using System.Reflection; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Discord.Commands.Builders; | using Discord.Commands.Builders; | ||||
| using Microsoft.Extensions.Logging; | |||||
| namespace Discord.Commands | namespace Discord.Commands | ||||
| { | { | ||||
| @@ -34,7 +35,7 @@ namespace Discord.Commands | |||||
| } | } | ||||
| else if (IsLoadableModule(typeInfo)) | else if (IsLoadableModule(typeInfo)) | ||||
| { | { | ||||
| await service._cmdLogger.WarningAsync($"Class {typeInfo.FullName} is not public and cannot be loaded. To suppress this message, mark the class with {nameof(DontAutoLoadAttribute)}.").ConfigureAwait(false); | |||||
| service._cmdLogger.LogWarning($"Class {typeInfo.FullName} is not public and cannot be loaded. To suppress this message, mark the class with {nameof(DontAutoLoadAttribute)}."); | |||||
| } | } | ||||
| } | } | ||||
| @@ -69,7 +70,7 @@ namespace Discord.Commands | |||||
| result[typeInfo.AsType()] = module.Build(service, services); | result[typeInfo.AsType()] = module.Build(service, services); | ||||
| } | } | ||||
| await service._cmdLogger.DebugAsync($"Successfully built {builtTypes.Count} modules.").ConfigureAwait(false); | |||||
| service._cmdLogger.LogDebug($"Successfully built {builtTypes.Count} modules."); | |||||
| return result; | return result; | ||||
| } | } | ||||
| @@ -8,6 +8,7 @@ using System.Threading; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Discord.Commands.Builders; | using Discord.Commands.Builders; | ||||
| using Discord.Logging; | using Discord.Logging; | ||||
| using Microsoft.Extensions.Logging; | |||||
| namespace Discord.Commands | namespace Discord.Commands | ||||
| { | { | ||||
| @@ -29,12 +30,6 @@ namespace Discord.Commands | |||||
| /// </remarks> | /// </remarks> | ||||
| public class CommandService : IDisposable | public class CommandService : IDisposable | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Occurs when a command-related information is received. | |||||
| /// </summary> | |||||
| public event Func<LogMessage, Task> Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } } | |||||
| internal readonly AsyncEvent<Func<LogMessage, Task>> _logEvent = new AsyncEvent<Func<LogMessage, Task>>(); | |||||
| /// <summary> | /// <summary> | ||||
| /// Occurs when a command is executed. | /// Occurs when a command is executed. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -56,8 +51,8 @@ namespace Discord.Commands | |||||
| internal readonly bool _caseSensitive, _throwOnError, _ignoreExtraArgs; | internal readonly bool _caseSensitive, _throwOnError, _ignoreExtraArgs; | ||||
| internal readonly char _separatorChar; | internal readonly char _separatorChar; | ||||
| internal readonly RunMode _defaultRunMode; | internal readonly RunMode _defaultRunMode; | ||||
| internal readonly Logger _cmdLogger; | |||||
| internal readonly LogManager _logManager; | |||||
| internal readonly ILogger _cmdLogger; | |||||
| internal readonly ILoggerFactory _logManager; | |||||
| internal readonly IReadOnlyDictionary<char, char> _quotationMarkAliasMap; | internal readonly IReadOnlyDictionary<char, char> _quotationMarkAliasMap; | ||||
| internal bool _isDisposed; | internal bool _isDisposed; | ||||
| @@ -100,8 +95,14 @@ namespace Discord.Commands | |||||
| if (_defaultRunMode == RunMode.Default) | if (_defaultRunMode == RunMode.Default) | ||||
| throw new InvalidOperationException("The default run mode cannot be set to Default."); | throw new InvalidOperationException("The default run mode cannot be set to Default."); | ||||
| _logManager = new LogManager(config.LogLevel); | |||||
| _logManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false); | |||||
| _logManager = config.LoggerFactory; | |||||
| if (_logManager == null) | |||||
| { | |||||
| _logManager = new DefaultLoggerFactory(); | |||||
| _logManager.AddProvider(new DefaultLoggerProvider()); | |||||
| } | |||||
| _cmdLogger = _logManager.CreateLogger("Command"); | _cmdLogger = _logManager.CreateLogger("Command"); | ||||
| _moduleLock = new SemaphoreSlim(1, 1); | _moduleLock = new SemaphoreSlim(1, 1); | ||||
| @@ -349,7 +350,8 @@ namespace Discord.Commands | |||||
| public void AddTypeReader(Type type, TypeReader reader) | public void AddTypeReader(Type type, TypeReader reader) | ||||
| { | { | ||||
| if (_defaultTypeReaders.ContainsKey(type)) | if (_defaultTypeReaders.ContainsKey(type)) | ||||
| _ = _cmdLogger.WarningAsync($"The default TypeReader for {type.FullName} was replaced by {reader.GetType().FullName}." + | |||||
| _cmdLogger.LogWarning( | |||||
| $"The default TypeReader for {type.FullName} was replaced by {reader.GetType().FullName}." + | |||||
| "To suppress this message, use AddTypeReader<T>(reader, true)."); | "To suppress this message, use AddTypeReader<T>(reader, true)."); | ||||
| AddTypeReader(type, reader, true); | AddTypeReader(type, reader, true); | ||||
| } | } | ||||
| @@ -1,3 +1,4 @@ | |||||
| using Microsoft.Extensions.Logging; | |||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| namespace Discord.Commands | namespace Discord.Commands | ||||
| @@ -23,10 +24,7 @@ namespace Discord.Commands | |||||
| /// </summary> | /// </summary> | ||||
| public bool CaseSensitiveCommands { get; set; } = false; | public bool CaseSensitiveCommands { get; set; } = false; | ||||
| /// <summary> | |||||
| /// Gets or sets the minimum log level severity that will be sent to the <see cref="CommandService.Log"/> event. | |||||
| /// </summary> | |||||
| public LogSeverity LogLevel { get; set; } = LogSeverity.Info; | |||||
| public ILoggerFactory LoggerFactory { get; set; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets or sets whether <see cref="RunMode.Sync"/> commands should push exceptions up to the caller. | /// Gets or sets whether <see cref="RunMode.Sync"/> commands should push exceptions up to the caller. | ||||
| @@ -1,4 +1,5 @@ | |||||
| using Discord.Commands.Builders; | using Discord.Commands.Builders; | ||||
| using Microsoft.Extensions.Logging; | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| @@ -240,7 +241,7 @@ namespace Discord.Commands | |||||
| private async Task<IResult> ExecuteInternalAsync(ICommandContext context, object[] args, IServiceProvider services) | private async Task<IResult> ExecuteInternalAsync(ICommandContext context, object[] args, IServiceProvider services) | ||||
| { | { | ||||
| await Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false); | |||||
| Module.Service._cmdLogger.LogDebug($"Executing {GetLogText(context)}"); | |||||
| try | try | ||||
| { | { | ||||
| var task = _action(context, args, services, this); | var task = _action(context, args, services, this); | ||||
| @@ -274,7 +275,7 @@ namespace Discord.Commands | |||||
| ex = ex.InnerException; | ex = ex.InnerException; | ||||
| var wrappedEx = new CommandException(this, context, ex); | var wrappedEx = new CommandException(this, context, ex); | ||||
| await Module.Service._cmdLogger.ErrorAsync(wrappedEx).ConfigureAwait(false); | |||||
| Module.Service._cmdLogger.LogError(wrappedEx, wrappedEx.Message); | |||||
| var result = ExecuteResult.FromError(ex); | var result = ExecuteResult.FromError(ex); | ||||
| await Module.Service._commandExecutedEvent.InvokeAsync(this, context, result).ConfigureAwait(false); | await Module.Service._commandExecutedEvent.InvokeAsync(this, context, result).ConfigureAwait(false); | ||||
| @@ -291,7 +292,7 @@ namespace Discord.Commands | |||||
| } | } | ||||
| finally | finally | ||||
| { | { | ||||
| await Module.Service._cmdLogger.VerboseAsync($"Executed {GetLogText(context)}").ConfigureAwait(false); | |||||
| Module.Service._cmdLogger.LogTrace($"Executed {GetLogText(context)}"); | |||||
| } | } | ||||
| } | } | ||||
| @@ -9,6 +9,7 @@ | |||||
| <TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks> | <TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks> | ||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" /> | |||||
| <PackageReference Include="Newtonsoft.Json" Version="12.0.2" /> | <PackageReference Include="Newtonsoft.Json" Version="12.0.2" /> | ||||
| <PackageReference Include="System.Collections.Immutable" Version="1.3.1" /> | <PackageReference Include="System.Collections.Immutable" Version="1.3.1" /> | ||||
| <PackageReference Include="System.Interactive.Async" Version="4.0.0" /> | <PackageReference Include="System.Interactive.Async" Version="4.0.0" /> | ||||
| @@ -1,3 +1,4 @@ | |||||
| using Microsoft.Extensions.Logging; | |||||
| using System.Reflection; | using System.Reflection; | ||||
| namespace Discord | namespace Discord | ||||
| @@ -123,14 +124,8 @@ namespace Discord | |||||
| /// The currently set <see cref="RetryMode"/>. | /// The currently set <see cref="RetryMode"/>. | ||||
| /// </returns> | /// </returns> | ||||
| public RetryMode DefaultRetryMode { get; set; } = RetryMode.AlwaysRetry; | public RetryMode DefaultRetryMode { get; set; } = RetryMode.AlwaysRetry; | ||||
| /// <summary> | |||||
| /// Gets or sets the minimum log level severity that will be sent to the Log event. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// The currently set <see cref="LogSeverity"/> for logging level. | |||||
| /// </returns> | |||||
| public LogSeverity LogLevel { get; set; } = LogSeverity.Info; | |||||
| public ILoggerFactory LoggerFactory { get; set; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets or sets whether the initial log entry should be printed. | /// Gets or sets whether the initial log entry should be printed. | ||||
| @@ -0,0 +1,52 @@ | |||||
| using Microsoft.Extensions.Logging; | |||||
| using System; | |||||
| namespace Discord.Logging | |||||
| { | |||||
| internal class DefaultLogger : ILogger | |||||
| { | |||||
| private static readonly object _lock = new object(); | |||||
| private LogLevel MinimumLevel { get; } | |||||
| internal DefaultLogger(LogLevel minLevel = LogLevel.Information) | |||||
| { | |||||
| this.MinimumLevel = minLevel; | |||||
| } | |||||
| public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) | |||||
| { | |||||
| if (!this.IsEnabled(logLevel)) | |||||
| return; | |||||
| lock (_lock) | |||||
| { | |||||
| Console.Write($"{DateTime.Now} "); | |||||
| Console.Write(logLevel switch | |||||
| { | |||||
| LogLevel.Trace => "[Trace] ", | |||||
| LogLevel.Debug => "[Debug] ", | |||||
| LogLevel.Information => "[Info ] ", | |||||
| LogLevel.Warning => "[Warn ] ", | |||||
| LogLevel.Error => "[Error] ", | |||||
| LogLevel.Critical => "[Crit ] ", | |||||
| LogLevel.None => "[None ] ", | |||||
| _ => "[?????] " | |||||
| }); | |||||
| var message = formatter(state, exception); | |||||
| Console.WriteLine(message); | |||||
| if (exception != null) | |||||
| Console.WriteLine(exception); | |||||
| } | |||||
| } | |||||
| public bool IsEnabled(LogLevel logLevel) => logLevel >= this.MinimumLevel; | |||||
| public IDisposable BeginScope<TState>(TState state) | |||||
| { | |||||
| throw new NotImplementedException(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,41 @@ | |||||
| using Microsoft.Extensions.Logging; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| namespace Discord.Logging | |||||
| { | |||||
| internal class DefaultLoggerFactory : ILoggerFactory | |||||
| { | |||||
| private List<ILoggerProvider> Providers { get; } = new List<ILoggerProvider>(); | |||||
| private bool _isDisposed = false; | |||||
| public void Dispose() | |||||
| { | |||||
| if (this._isDisposed) | |||||
| return; | |||||
| this._isDisposed = true; | |||||
| foreach (var provider in this.Providers) | |||||
| provider.Dispose(); | |||||
| this.Providers.Clear(); | |||||
| } | |||||
| public ILogger CreateLogger(string categoryName) | |||||
| { | |||||
| if (this._isDisposed) | |||||
| throw new InvalidOperationException("This logger factory is already disposed."); | |||||
| // HEHEHE XDXD | |||||
| var provider = Providers.FirstOrDefault(); | |||||
| return provider?.CreateLogger(categoryName) ?? throw new ArgumentNullException(nameof(provider)); | |||||
| } | |||||
| public void AddProvider(ILoggerProvider provider) | |||||
| { | |||||
| this.Providers.Add(provider); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,31 @@ | |||||
| using Microsoft.Extensions.Logging; | |||||
| using System; | |||||
| namespace Discord.Logging | |||||
| { | |||||
| internal class DefaultLoggerProvider : ILoggerProvider | |||||
| { | |||||
| private LogLevel MinimumLevel { get; } | |||||
| private bool _isDisposed = false; | |||||
| internal DefaultLoggerProvider(LogLevel minLevel = LogLevel.Information) | |||||
| { | |||||
| this.MinimumLevel = minLevel; | |||||
| } | |||||
| public ILogger CreateLogger(string categoryName) | |||||
| { | |||||
| if (this._isDisposed) | |||||
| throw new InvalidOperationException("This logger provider is already disposed."); | |||||
| return new DefaultLogger(this.MinimumLevel); | |||||
| } | |||||
| public void Dispose() | |||||
| { | |||||
| this._isDisposed = true; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,105 +0,0 @@ | |||||
| using System; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Logging | |||||
| { | |||||
| internal class LogManager | |||||
| { | |||||
| public LogSeverity Level { get; } | |||||
| private Logger ClientLogger { get; } | |||||
| public event Func<LogMessage, Task> Message { add { _messageEvent.Add(value); } remove { _messageEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<LogMessage, Task>> _messageEvent = new AsyncEvent<Func<LogMessage, Task>>(); | |||||
| public LogManager(LogSeverity minSeverity) | |||||
| { | |||||
| Level = minSeverity; | |||||
| ClientLogger = new Logger(this, "Discord"); | |||||
| } | |||||
| public async Task LogAsync(LogSeverity severity, string source, Exception ex) | |||||
| { | |||||
| try | |||||
| { | |||||
| if (severity <= Level) | |||||
| await _messageEvent.InvokeAsync(new LogMessage(severity, source, null, ex)).ConfigureAwait(false); | |||||
| } | |||||
| catch | |||||
| { | |||||
| // ignored | |||||
| } | |||||
| } | |||||
| public async Task LogAsync(LogSeverity severity, string source, string message, Exception ex = null) | |||||
| { | |||||
| try | |||||
| { | |||||
| if (severity <= Level) | |||||
| await _messageEvent.InvokeAsync(new LogMessage(severity, source, message, ex)).ConfigureAwait(false); | |||||
| } | |||||
| catch | |||||
| { | |||||
| // ignored | |||||
| } | |||||
| } | |||||
| public async Task LogAsync(LogSeverity severity, string source, FormattableString message, Exception ex = null) | |||||
| { | |||||
| try | |||||
| { | |||||
| if (severity <= Level) | |||||
| await _messageEvent.InvokeAsync(new LogMessage(severity, source, message.ToString(), ex)).ConfigureAwait(false); | |||||
| } | |||||
| catch { } | |||||
| } | |||||
| public Task ErrorAsync(string source, Exception ex) | |||||
| => LogAsync(LogSeverity.Error, source, ex); | |||||
| public Task ErrorAsync(string source, string message, Exception ex = null) | |||||
| => LogAsync(LogSeverity.Error, source, message, ex); | |||||
| public Task ErrorAsync(string source, FormattableString message, Exception ex = null) | |||||
| => LogAsync(LogSeverity.Error, source, message, ex); | |||||
| public Task WarningAsync(string source, Exception ex) | |||||
| => LogAsync(LogSeverity.Warning, source, ex); | |||||
| public Task WarningAsync(string source, string message, Exception ex = null) | |||||
| => LogAsync(LogSeverity.Warning, source, message, ex); | |||||
| public Task WarningAsync(string source, FormattableString message, Exception ex = null) | |||||
| => LogAsync(LogSeverity.Warning, source, message, ex); | |||||
| public Task InfoAsync(string source, Exception ex) | |||||
| => LogAsync(LogSeverity.Info, source, ex); | |||||
| public Task InfoAsync(string source, string message, Exception ex = null) | |||||
| => LogAsync(LogSeverity.Info, source, message, ex); | |||||
| public Task InfoAsync(string source, FormattableString message, Exception ex = null) | |||||
| => LogAsync(LogSeverity.Info, source, message, ex); | |||||
| public Task VerboseAsync(string source, Exception ex) | |||||
| => LogAsync(LogSeverity.Verbose, source, ex); | |||||
| public Task VerboseAsync(string source, string message, Exception ex = null) | |||||
| => LogAsync(LogSeverity.Verbose, source, message, ex); | |||||
| public Task VerboseAsync(string source, FormattableString message, Exception ex = null) | |||||
| => LogAsync(LogSeverity.Verbose, source, message, ex); | |||||
| public Task DebugAsync(string source, Exception ex) | |||||
| => LogAsync(LogSeverity.Debug, source, ex); | |||||
| public Task DebugAsync(string source, string message, Exception ex = null) | |||||
| => LogAsync(LogSeverity.Debug, source, message, ex); | |||||
| public Task DebugAsync(string source, FormattableString message, Exception ex = null) | |||||
| => LogAsync(LogSeverity.Debug, source, message, ex); | |||||
| public Logger CreateLogger(string name) => new Logger(this, name); | |||||
| public async Task WriteInitialLog() | |||||
| { | |||||
| await ClientLogger.InfoAsync($"Discord.Net v{DiscordConfig.Version} (API v{DiscordConfig.APIVersion})").ConfigureAwait(false); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,136 +0,0 @@ | |||||
| using System; | |||||
| using System.Text; | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// Provides a message object used for logging purposes. | |||||
| /// </summary> | |||||
| public struct LogMessage | |||||
| { | |||||
| /// <summary> | |||||
| /// Gets the severity of the log entry. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A <see cref="LogSeverity"/> enum to indicate the severeness of the incident or event. | |||||
| /// </returns> | |||||
| public LogSeverity Severity { get; } | |||||
| /// <summary> | |||||
| /// Gets the source of the log entry. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A string representing the source of the log entry. | |||||
| /// </returns> | |||||
| public string Source { get; } | |||||
| /// <summary> | |||||
| /// Gets the message of this log entry. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A string containing the message of this log entry. | |||||
| /// </returns> | |||||
| public string Message { get; } | |||||
| /// <summary> | |||||
| /// Gets the exception of this log entry. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// An <see cref="Discord.LogMessage.Exception" /> object associated with an incident; otherwise <c>null</c>. | |||||
| /// </returns> | |||||
| public Exception Exception { get; } | |||||
| /// <summary> | |||||
| /// Initializes a new <see cref="LogMessage"/> struct with the severity, source, message of the event, and | |||||
| /// optionally, an exception. | |||||
| /// </summary> | |||||
| /// <param name="severity">The severity of the event.</param> | |||||
| /// <param name="source">The source of the event.</param> | |||||
| /// <param name="message">The message of the event.</param> | |||||
| /// <param name="exception">The exception of the event.</param> | |||||
| public LogMessage(LogSeverity severity, string source, string message, Exception exception = null) | |||||
| { | |||||
| Severity = severity; | |||||
| Source = source; | |||||
| Message = message; | |||||
| Exception = exception; | |||||
| } | |||||
| public override string ToString() => ToString(); | |||||
| public string ToString(StringBuilder builder = null, bool fullException = true, bool prependTimestamp = true, DateTimeKind timestampKind = DateTimeKind.Local, int? padSource = 11) | |||||
| { | |||||
| string sourceName = Source; | |||||
| string message = Message; | |||||
| string exMessage = fullException ? Exception?.ToString() : Exception?.Message; | |||||
| 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 (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 (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)) | |||||
| { | |||||
| for (int i = 0; i < message.Length; i++) | |||||
| { | |||||
| //Strip control chars | |||||
| char c = message[i]; | |||||
| if (!char.IsControl(c)) | |||||
| builder.Append(c); | |||||
| } | |||||
| } | |||||
| if (exMessage != null) | |||||
| { | |||||
| if (!string.IsNullOrEmpty(Message)) | |||||
| { | |||||
| builder.Append(':'); | |||||
| builder.AppendLine(); | |||||
| } | |||||
| builder.Append(exMessage); | |||||
| } | |||||
| return builder.ToString(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,34 +0,0 @@ | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// Specifies the severity of the log message. | |||||
| /// </summary> | |||||
| public enum LogSeverity | |||||
| { | |||||
| /// <summary> | |||||
| /// Logs that contain the most severe level of error. This type of error indicate that immediate attention | |||||
| /// may be required. | |||||
| /// </summary> | |||||
| Critical = 0, | |||||
| /// <summary> | |||||
| /// Logs that highlight when the flow of execution is stopped due to a failure. | |||||
| /// </summary> | |||||
| Error = 1, | |||||
| /// <summary> | |||||
| /// Logs that highlight an abnormal activity in the flow of execution. | |||||
| /// </summary> | |||||
| Warning = 2, | |||||
| /// <summary> | |||||
| /// Logs that track the general flow of the application. | |||||
| /// </summary> | |||||
| Info = 3, | |||||
| /// <summary> | |||||
| /// Logs that are used for interactive investigation during development. | |||||
| /// </summary> | |||||
| Verbose = 4, | |||||
| /// <summary> | |||||
| /// Logs that contain the most detailed messages. | |||||
| /// </summary> | |||||
| Debug = 5 | |||||
| } | |||||
| } | |||||
| @@ -1,72 +0,0 @@ | |||||
| using System; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Logging | |||||
| { | |||||
| internal class Logger | |||||
| { | |||||
| private readonly LogManager _manager; | |||||
| public string Name { get; } | |||||
| public LogSeverity Level => _manager.Level; | |||||
| public Logger(LogManager manager, string name) | |||||
| { | |||||
| _manager = manager; | |||||
| Name = name; | |||||
| } | |||||
| public Task LogAsync(LogSeverity severity, Exception exception = null) | |||||
| => _manager.LogAsync(severity, Name, exception); | |||||
| public Task LogAsync(LogSeverity severity, string message, Exception exception = null) | |||||
| => _manager.LogAsync(severity, Name, message, exception); | |||||
| public Task LogAsync(LogSeverity severity, FormattableString message, Exception exception = null) | |||||
| => _manager.LogAsync(severity, Name, message, exception); | |||||
| public Task ErrorAsync(Exception exception) | |||||
| => _manager.ErrorAsync(Name, exception); | |||||
| public Task ErrorAsync(string message, Exception exception = null) | |||||
| => _manager.ErrorAsync(Name, message, exception); | |||||
| public Task ErrorAsync(FormattableString message, Exception exception = null) | |||||
| => _manager.ErrorAsync(Name, message, exception); | |||||
| public Task WarningAsync(Exception exception) | |||||
| => _manager.WarningAsync(Name, exception); | |||||
| public Task WarningAsync(string message, Exception exception = null) | |||||
| => _manager.WarningAsync(Name, message, exception); | |||||
| public Task WarningAsync(FormattableString message, Exception exception = null) | |||||
| => _manager.WarningAsync(Name, message, exception); | |||||
| public Task InfoAsync(Exception exception) | |||||
| => _manager.InfoAsync(Name, exception); | |||||
| public Task InfoAsync(string message, Exception exception = null) | |||||
| => _manager.InfoAsync(Name, message, exception); | |||||
| public Task InfoAsync(FormattableString message, Exception exception = null) | |||||
| => _manager.InfoAsync(Name, message, exception); | |||||
| public Task VerboseAsync(Exception exception) | |||||
| => _manager.VerboseAsync(Name, exception); | |||||
| public Task VerboseAsync(string message, Exception exception = null) | |||||
| => _manager.VerboseAsync(Name, message, exception); | |||||
| public Task VerboseAsync(FormattableString message, Exception exception = null) | |||||
| => _manager.VerboseAsync(Name, message, exception); | |||||
| public Task DebugAsync(Exception exception) | |||||
| => _manager.DebugAsync(Name, exception); | |||||
| public Task DebugAsync(string message, Exception exception = null) | |||||
| => _manager.DebugAsync(Name, message, exception); | |||||
| public Task DebugAsync(FormattableString message, Exception exception = null) | |||||
| => _manager.DebugAsync(Name, message, exception); | |||||
| } | |||||
| } | |||||
| @@ -1,4 +1,5 @@ | |||||
| using Discord.Logging; | using Discord.Logging; | ||||
| using Microsoft.Extensions.Logging; | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| @@ -10,20 +11,17 @@ namespace Discord.Rest | |||||
| { | { | ||||
| public abstract class BaseDiscordClient : IDiscordClient | public abstract class BaseDiscordClient : IDiscordClient | ||||
| { | { | ||||
| public event Func<LogMessage, Task> Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } } | |||||
| internal readonly AsyncEvent<Func<LogMessage, Task>> _logEvent = new AsyncEvent<Func<LogMessage, Task>>(); | |||||
| public event Func<Task> LoggedIn { add { _loggedInEvent.Add(value); } remove { _loggedInEvent.Remove(value); } } | public event Func<Task> LoggedIn { add { _loggedInEvent.Add(value); } remove { _loggedInEvent.Remove(value); } } | ||||
| private readonly AsyncEvent<Func<Task>> _loggedInEvent = new AsyncEvent<Func<Task>>(); | private readonly AsyncEvent<Func<Task>> _loggedInEvent = new AsyncEvent<Func<Task>>(); | ||||
| 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 Logger _restLogger; | |||||
| internal readonly ILogger _restLogger; | |||||
| private readonly SemaphoreSlim _stateLock; | private readonly SemaphoreSlim _stateLock; | ||||
| private bool _isFirstLogin, _isDisposed; | private bool _isFirstLogin, _isDisposed; | ||||
| internal API.DiscordRestApiClient ApiClient { get; } | internal API.DiscordRestApiClient ApiClient { get; } | ||||
| internal LogManager LogManager { get; } | |||||
| internal ILoggerFactory LogManager { get; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets the login state of the client. | /// Gets the login state of the client. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -39,21 +37,32 @@ namespace Discord.Rest | |||||
| internal BaseDiscordClient(DiscordRestConfig config, API.DiscordRestApiClient client) | internal BaseDiscordClient(DiscordRestConfig config, API.DiscordRestApiClient client) | ||||
| { | { | ||||
| ApiClient = client; | ApiClient = client; | ||||
| LogManager = new LogManager(config.LogLevel); | |||||
| LogManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false); | |||||
| LogManager = config.LoggerFactory; | |||||
| if (LogManager == null) | |||||
| { | |||||
| LogManager = new DefaultLoggerFactory(); | |||||
| LogManager.AddProvider(new DefaultLoggerProvider()); | |||||
| } | |||||
| _stateLock = new SemaphoreSlim(1, 1); | _stateLock = new SemaphoreSlim(1, 1); | ||||
| _restLogger = LogManager.CreateLogger("Rest"); | _restLogger = LogManager.CreateLogger("Rest"); | ||||
| _isFirstLogin = config.DisplayInitialLog; | _isFirstLogin = config.DisplayInitialLog; | ||||
| ApiClient.RequestQueue.RateLimitTriggered += async (id, info, endpoint) => | |||||
| ApiClient.RequestQueue.RateLimitTriggered += (id, info, endpoint) => | |||||
| { | { | ||||
| if (info == null) | if (info == null) | ||||
| await _restLogger.VerboseAsync($"Preemptive Rate limit triggered: {endpoint} {(id.IsHashBucket ? $"(Bucket: {id.BucketHash})" : "")}").ConfigureAwait(false); | |||||
| _restLogger.LogTrace($"Preemptive Rate limit triggered: {endpoint} {(id.IsHashBucket ? $"(Bucket: {id.BucketHash})" : "")}"); | |||||
| else | else | ||||
| await _restLogger.WarningAsync($"Rate limit triggered: {endpoint} {(id.IsHashBucket ? $"(Bucket: {id.BucketHash})" : "")}").ConfigureAwait(false); | |||||
| _restLogger.LogTrace($"Rate limit triggered: {endpoint} {(id.IsHashBucket ? $"(Bucket: {id.BucketHash})" : "")}"); | |||||
| return Task.CompletedTask; | |||||
| }; | |||||
| ApiClient.SentRequest += (method, endpoint, millis) => | |||||
| { | |||||
| _restLogger.LogTrace($"{method} {endpoint}: {millis} ms"); | |||||
| return Task.CompletedTask; | |||||
| }; | }; | ||||
| ApiClient.SentRequest += async (method, endpoint, millis) => await _restLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false); | |||||
| } | } | ||||
| public async Task LoginAsync(TokenType tokenType, string token, bool validateToken = true) | public async Task LoginAsync(TokenType tokenType, string token, bool validateToken = true) | ||||
| @@ -70,7 +79,7 @@ namespace Discord.Rest | |||||
| if (_isFirstLogin) | if (_isFirstLogin) | ||||
| { | { | ||||
| _isFirstLogin = false; | _isFirstLogin = false; | ||||
| await LogManager.WriteInitialLog().ConfigureAwait(false); | |||||
| _restLogger.LogInformation($"Discord.Net v{DiscordConfig.Version} (API v{DiscordConfig.APIVersion})"); | |||||
| } | } | ||||
| if (LoginState != LoginState.LoggedOut) | if (LoginState != LoginState.LoggedOut) | ||||
| @@ -90,7 +99,7 @@ namespace Discord.Rest | |||||
| catch (ArgumentException ex) | catch (ArgumentException ex) | ||||
| { | { | ||||
| // log these ArgumentExceptions and allow for the client to attempt to log in anyways | // log these ArgumentExceptions and allow for the client to attempt to log in anyways | ||||
| await LogManager.WarningAsync("Discord", "A supplied token was invalid.", ex).ConfigureAwait(false); | |||||
| _restLogger.LogWarning(ex, "A supplied token was invalid."); | |||||
| } | } | ||||
| } | } | ||||
| @@ -3,6 +3,7 @@ using Discord.Audio.Streams; | |||||
| using Discord.Logging; | using Discord.Logging; | ||||
| using Discord.Net.Converters; | using Discord.Net.Converters; | ||||
| using Discord.WebSocket; | using Discord.WebSocket; | ||||
| using Microsoft.Extensions.Logging; | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using Newtonsoft.Json.Linq; | using Newtonsoft.Json.Linq; | ||||
| using System; | using System; | ||||
| @@ -30,7 +31,7 @@ namespace Discord.Audio | |||||
| } | } | ||||
| } | } | ||||
| private readonly Logger _audioLogger; | |||||
| private readonly ILogger _audioLogger; | |||||
| private readonly JsonSerializer _serializer; | private readonly JsonSerializer _serializer; | ||||
| private readonly ConnectionManager _connection; | private readonly ConnectionManager _connection; | ||||
| private readonly SemaphoreSlim _stateLock; | private readonly SemaphoreSlim _stateLock; | ||||
| @@ -64,8 +65,16 @@ namespace Discord.Audio | |||||
| _audioLogger = Discord.LogManager.CreateLogger($"Audio #{clientId}"); | _audioLogger = Discord.LogManager.CreateLogger($"Audio #{clientId}"); | ||||
| ApiClient = new DiscordVoiceAPIClient(guild.Id, Discord.WebSocketProvider, Discord.UdpSocketProvider); | ApiClient = new DiscordVoiceAPIClient(guild.Id, Discord.WebSocketProvider, Discord.UdpSocketProvider); | ||||
| ApiClient.SentGatewayMessage += async opCode => await _audioLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); | |||||
| ApiClient.SentDiscovery += async () => await _audioLogger.DebugAsync("Sent Discovery").ConfigureAwait(false); | |||||
| ApiClient.SentGatewayMessage += opCode => | |||||
| { | |||||
| _audioLogger.LogDebug($"Sent {opCode}"); | |||||
| return Task.CompletedTask; | |||||
| }; | |||||
| ApiClient.SentDiscovery += () => | |||||
| { | |||||
| _audioLogger.LogDebug("Sent Discovery"); | |||||
| return Task.CompletedTask; | |||||
| }; | |||||
| //ApiClient.SentData += async bytes => await _audioLogger.DebugAsync($"Sent {bytes} Bytes").ConfigureAwait(false); | //ApiClient.SentData += async bytes => await _audioLogger.DebugAsync($"Sent {bytes} Bytes").ConfigureAwait(false); | ||||
| ApiClient.ReceivedEvent += ProcessMessageAsync; | ApiClient.ReceivedEvent += ProcessMessageAsync; | ||||
| ApiClient.ReceivedPacket += ProcessPacketAsync; | ApiClient.ReceivedPacket += ProcessPacketAsync; | ||||
| @@ -83,12 +92,20 @@ namespace Discord.Audio | |||||
| _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | ||||
| _serializer.Error += (s, e) => | _serializer.Error += (s, e) => | ||||
| { | { | ||||
| _audioLogger.WarningAsync(e.ErrorContext.Error).GetAwaiter().GetResult(); | |||||
| _audioLogger.LogWarning(e.ErrorContext.Error, e.ErrorContext.Error.Message); | |||||
| e.ErrorContext.Handled = true; | e.ErrorContext.Handled = true; | ||||
| }; | }; | ||||
| LatencyUpdated += async (old, val) => await _audioLogger.DebugAsync($"Latency = {val} ms").ConfigureAwait(false); | |||||
| UdpLatencyUpdated += async (old, val) => await _audioLogger.DebugAsync($"UDP Latency = {val} ms").ConfigureAwait(false); | |||||
| LatencyUpdated += (old, val) => | |||||
| { | |||||
| _audioLogger.LogDebug($"Latency = {val} ms"); | |||||
| return Task.CompletedTask; | |||||
| }; | |||||
| UdpLatencyUpdated += (old, val) => | |||||
| { | |||||
| _audioLogger.LogDebug($"UDP Latency = {val} ms"); | |||||
| return Task.CompletedTask; | |||||
| }; | |||||
| } | } | ||||
| internal async Task StartAsync(string url, ulong userId, string sessionId, string token) | internal async Task StartAsync(string url, ulong userId, string sessionId, string token) | ||||
| @@ -112,10 +129,10 @@ namespace Discord.Audio | |||||
| private async Task OnConnectingAsync() | private async Task OnConnectingAsync() | ||||
| { | { | ||||
| await _audioLogger.DebugAsync("Connecting ApiClient").ConfigureAwait(false); | |||||
| _audioLogger.LogDebug("Connecting ApiClient"); | |||||
| await ApiClient.ConnectAsync("wss://" + _url + "?v=" + DiscordConfig.VoiceAPIVersion).ConfigureAwait(false); | await ApiClient.ConnectAsync("wss://" + _url + "?v=" + DiscordConfig.VoiceAPIVersion).ConfigureAwait(false); | ||||
| await _audioLogger.DebugAsync("Listening on port " + ApiClient.UdpPort).ConfigureAwait(false); | |||||
| await _audioLogger.DebugAsync("Sending Identity").ConfigureAwait(false); | |||||
| _audioLogger.LogDebug("Listening on port " + ApiClient.UdpPort); | |||||
| _audioLogger.LogDebug("Sending Identity"); | |||||
| await ApiClient.SendIdentityAsync(_userId, _sessionId, _token).ConfigureAwait(false); | await ApiClient.SendIdentityAsync(_userId, _sessionId, _token).ConfigureAwait(false); | ||||
| //Wait for READY | //Wait for READY | ||||
| @@ -123,11 +140,11 @@ namespace Discord.Audio | |||||
| } | } | ||||
| private async Task OnDisconnectingAsync(Exception ex) | private async Task OnDisconnectingAsync(Exception ex) | ||||
| { | { | ||||
| await _audioLogger.DebugAsync("Disconnecting ApiClient").ConfigureAwait(false); | |||||
| _audioLogger.LogDebug("Disconnecting ApiClient"); | |||||
| await ApiClient.DisconnectAsync().ConfigureAwait(false); | await ApiClient.DisconnectAsync().ConfigureAwait(false); | ||||
| //Wait for tasks to complete | //Wait for tasks to complete | ||||
| await _audioLogger.DebugAsync("Waiting for heartbeater").ConfigureAwait(false); | |||||
| _audioLogger.LogDebug("Waiting for heartbeater"); | |||||
| var heartbeatTask = _heartbeatTask; | var heartbeatTask = _heartbeatTask; | ||||
| if (heartbeatTask != null) | if (heartbeatTask != null) | ||||
| await heartbeatTask.ConfigureAwait(false); | await heartbeatTask.ConfigureAwait(false); | ||||
| @@ -142,7 +159,7 @@ namespace Discord.Audio | |||||
| await ClearInputStreamsAsync().ConfigureAwait(false); | await ClearInputStreamsAsync().ConfigureAwait(false); | ||||
| await _audioLogger.DebugAsync("Sending Voice State").ConfigureAwait(false); | |||||
| _audioLogger.LogDebug("Sending Voice State"); | |||||
| await Discord.ApiClient.SendVoiceStateUpdateAsync(Guild.Id, null, false, false).ConfigureAwait(false); | await Discord.ApiClient.SendVoiceStateUpdateAsync(Guild.Id, null, false, false).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -224,7 +241,7 @@ namespace Discord.Audio | |||||
| { | { | ||||
| case VoiceOpCode.Ready: | case VoiceOpCode.Ready: | ||||
| { | { | ||||
| await _audioLogger.DebugAsync("Received Ready").ConfigureAwait(false); | |||||
| _audioLogger.LogDebug("Received Ready"); | |||||
| var data = (payload as JToken).ToObject<ReadyEvent>(_serializer); | var data = (payload as JToken).ToObject<ReadyEvent>(_serializer); | ||||
| _ssrc = data.SSRC; | _ssrc = data.SSRC; | ||||
| @@ -241,7 +258,7 @@ namespace Discord.Audio | |||||
| break; | break; | ||||
| case VoiceOpCode.SessionDescription: | case VoiceOpCode.SessionDescription: | ||||
| { | { | ||||
| await _audioLogger.DebugAsync("Received SessionDescription").ConfigureAwait(false); | |||||
| _audioLogger.LogDebug("Received SessionDescription"); | |||||
| var data = (payload as JToken).ToObject<SessionDescriptionEvent>(_serializer); | var data = (payload as JToken).ToObject<SessionDescriptionEvent>(_serializer); | ||||
| if (data.Mode != DiscordVoiceAPIClient.Mode) | if (data.Mode != DiscordVoiceAPIClient.Mode) | ||||
| @@ -257,7 +274,7 @@ namespace Discord.Audio | |||||
| break; | break; | ||||
| case VoiceOpCode.HeartbeatAck: | case VoiceOpCode.HeartbeatAck: | ||||
| { | { | ||||
| await _audioLogger.DebugAsync("Received HeartbeatAck").ConfigureAwait(false); | |||||
| _audioLogger.LogDebug("Received HeartbeatAck"); | |||||
| if (_heartbeatTimes.TryDequeue(out long time)) | if (_heartbeatTimes.TryDequeue(out long time)) | ||||
| { | { | ||||
| @@ -271,7 +288,7 @@ namespace Discord.Audio | |||||
| break; | break; | ||||
| case VoiceOpCode.Speaking: | case VoiceOpCode.Speaking: | ||||
| { | { | ||||
| await _audioLogger.DebugAsync("Received Speaking").ConfigureAwait(false); | |||||
| _audioLogger.LogDebug("Received Speaking"); | |||||
| var data = (payload as JToken).ToObject<SpeakingEvent>(_serializer); | var data = (payload as JToken).ToObject<SpeakingEvent>(_serializer); | ||||
| _ssrcMap[data.Ssrc] = data.UserId; //TODO: Memory Leak: SSRCs are never cleaned up | _ssrcMap[data.Ssrc] = data.UserId; //TODO: Memory Leak: SSRCs are never cleaned up | ||||
| @@ -280,13 +297,13 @@ namespace Discord.Audio | |||||
| } | } | ||||
| break; | break; | ||||
| default: | default: | ||||
| await _audioLogger.WarningAsync($"Unknown OpCode ({opCode})").ConfigureAwait(false); | |||||
| _audioLogger.LogWarning($"Unknown OpCode ({opCode})"); | |||||
| return; | return; | ||||
| } | } | ||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| { | { | ||||
| await _audioLogger.ErrorAsync($"Error handling {opCode}", ex).ConfigureAwait(false); | |||||
| _audioLogger.LogError($"Error handling {opCode}", ex); | |||||
| return; | return; | ||||
| } | } | ||||
| } | } | ||||
| @@ -298,7 +315,7 @@ namespace Discord.Audio | |||||
| { | { | ||||
| if (packet.Length != 70) | if (packet.Length != 70) | ||||
| { | { | ||||
| await _audioLogger.DebugAsync("Malformed Packet").ConfigureAwait(false); | |||||
| _audioLogger.LogDebug("Malformed Packet"); | |||||
| return; | return; | ||||
| } | } | ||||
| string ip; | string ip; | ||||
| @@ -310,18 +327,18 @@ namespace Discord.Audio | |||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| { | { | ||||
| await _audioLogger.DebugAsync("Malformed Packet", ex).ConfigureAwait(false); | |||||
| _audioLogger.LogDebug("Malformed Packet", ex); | |||||
| return; | return; | ||||
| } | } | ||||
| await _audioLogger.DebugAsync("Received Discovery").ConfigureAwait(false); | |||||
| _audioLogger.LogDebug("Received Discovery"); | |||||
| await ApiClient.SendSelectProtocol(ip, port).ConfigureAwait(false); | await ApiClient.SendSelectProtocol(ip, port).ConfigureAwait(false); | ||||
| } | } | ||||
| else if (_connection.State == ConnectionState.Connected) | else if (_connection.State == ConnectionState.Connected) | ||||
| { | { | ||||
| if (packet.Length == 8) | if (packet.Length == 8) | ||||
| { | { | ||||
| await _audioLogger.DebugAsync("Received Keepalive").ConfigureAwait(false); | |||||
| _audioLogger.LogDebug("Received Keepalive"); | |||||
| ulong value = | ulong value = | ||||
| ((ulong)packet[0] >> 0) | | ((ulong)packet[0] >> 0) | | ||||
| @@ -350,17 +367,17 @@ namespace Discord.Audio | |||||
| { | { | ||||
| if (!RTPReadStream.TryReadSsrc(packet, 0, out var ssrc)) | if (!RTPReadStream.TryReadSsrc(packet, 0, out var ssrc)) | ||||
| { | { | ||||
| await _audioLogger.DebugAsync("Malformed Frame").ConfigureAwait(false); | |||||
| _audioLogger.LogDebug("Malformed Frame"); | |||||
| return; | return; | ||||
| } | } | ||||
| if (!_ssrcMap.TryGetValue(ssrc, out var userId)) | if (!_ssrcMap.TryGetValue(ssrc, out var userId)) | ||||
| { | { | ||||
| await _audioLogger.DebugAsync($"Unknown SSRC {ssrc}").ConfigureAwait(false); | |||||
| _audioLogger.LogDebug($"Unknown SSRC {ssrc}"); | |||||
| return; | return; | ||||
| } | } | ||||
| if (!_streams.TryGetValue(userId, out var pair)) | if (!_streams.TryGetValue(userId, out var pair)) | ||||
| { | { | ||||
| await _audioLogger.DebugAsync($"Unknown User {userId}").ConfigureAwait(false); | |||||
| _audioLogger.LogDebug($"Unknown User {userId}"); | |||||
| return; | return; | ||||
| } | } | ||||
| try | try | ||||
| @@ -369,7 +386,7 @@ namespace Discord.Audio | |||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| { | { | ||||
| await _audioLogger.DebugAsync("Malformed Frame", ex).ConfigureAwait(false); | |||||
| _audioLogger.LogDebug(ex, "Malformed Frame"); | |||||
| return; | return; | ||||
| } | } | ||||
| //await _audioLogger.DebugAsync($"Received {packet.Length} bytes from user {userId}").ConfigureAwait(false); | //await _audioLogger.DebugAsync($"Received {packet.Length} bytes from user {userId}").ConfigureAwait(false); | ||||
| @@ -378,7 +395,7 @@ namespace Discord.Audio | |||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| { | { | ||||
| await _audioLogger.WarningAsync("Failed to process UDP packet", ex).ConfigureAwait(false); | |||||
| _audioLogger.LogWarning("Failed to process UDP packet", ex); | |||||
| return; | return; | ||||
| } | } | ||||
| } | } | ||||
| @@ -388,7 +405,7 @@ namespace Discord.Audio | |||||
| // TODO: Clean this up when Discord's session patch is live | // TODO: Clean this up when Discord's session patch is live | ||||
| try | try | ||||
| { | { | ||||
| await _audioLogger.DebugAsync("Heartbeat Started").ConfigureAwait(false); | |||||
| _audioLogger.LogDebug("Heartbeat Started"); | |||||
| while (!cancelToken.IsCancellationRequested) | while (!cancelToken.IsCancellationRequested) | ||||
| { | { | ||||
| var now = Environment.TickCount; | var now = Environment.TickCount; | ||||
| @@ -408,27 +425,27 @@ namespace Discord.Audio | |||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| { | { | ||||
| await _audioLogger.WarningAsync("Failed to send heartbeat", ex).ConfigureAwait(false); | |||||
| _audioLogger.LogWarning("Failed to send heartbeat", ex); | |||||
| } | } | ||||
| await Task.Delay(intervalMillis, cancelToken).ConfigureAwait(false); | await Task.Delay(intervalMillis, cancelToken).ConfigureAwait(false); | ||||
| } | } | ||||
| await _audioLogger.DebugAsync("Heartbeat Stopped").ConfigureAwait(false); | |||||
| _audioLogger.LogDebug("Heartbeat Stopped"); | |||||
| } | } | ||||
| catch (OperationCanceledException) | catch (OperationCanceledException) | ||||
| { | { | ||||
| await _audioLogger.DebugAsync("Heartbeat Stopped").ConfigureAwait(false); | |||||
| _audioLogger.LogDebug("Heartbeat Stopped"); | |||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| { | { | ||||
| await _audioLogger.ErrorAsync("Heartbeat Errored", ex).ConfigureAwait(false); | |||||
| _audioLogger.LogError("Heartbeat Errored", ex); | |||||
| } | } | ||||
| } | } | ||||
| private async Task RunKeepaliveAsync(int intervalMillis, CancellationToken cancelToken) | private async Task RunKeepaliveAsync(int intervalMillis, CancellationToken cancelToken) | ||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| await _audioLogger.DebugAsync("Keepalive Started").ConfigureAwait(false); | |||||
| _audioLogger.LogDebug("Keepalive Started"); | |||||
| while (!cancelToken.IsCancellationRequested) | while (!cancelToken.IsCancellationRequested) | ||||
| { | { | ||||
| var now = Environment.TickCount; | var now = Environment.TickCount; | ||||
| @@ -441,20 +458,20 @@ namespace Discord.Audio | |||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| { | { | ||||
| await _audioLogger.WarningAsync("Failed to send keepalive", ex).ConfigureAwait(false); | |||||
| _audioLogger.LogWarning("Failed to send keepalive", ex); | |||||
| } | } | ||||
| await Task.Delay(intervalMillis, cancelToken).ConfigureAwait(false); | await Task.Delay(intervalMillis, cancelToken).ConfigureAwait(false); | ||||
| } | } | ||||
| await _audioLogger.DebugAsync("Keepalive Stopped").ConfigureAwait(false); | |||||
| _audioLogger.LogDebug("Keepalive Stopped"); | |||||
| } | } | ||||
| catch (OperationCanceledException) | catch (OperationCanceledException) | ||||
| { | { | ||||
| await _audioLogger.DebugAsync("Keepalive Stopped").ConfigureAwait(false); | |||||
| _audioLogger.LogDebug("Keepalive Stopped"); | |||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| { | { | ||||
| await _audioLogger.ErrorAsync("Keepalive Errored", ex).ConfigureAwait(false); | |||||
| _audioLogger.LogError("Keepalive Errored", ex); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,5 @@ | |||||
| using Discord.Logging; | using Discord.Logging; | ||||
| using Microsoft.Extensions.Logging; | |||||
| using System; | using System; | ||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
| using System.Threading; | using System.Threading; | ||||
| @@ -33,14 +34,14 @@ namespace Discord.Audio.Streams | |||||
| private readonly ConcurrentQueue<Frame> _queuedFrames; | private readonly ConcurrentQueue<Frame> _queuedFrames; | ||||
| private readonly ConcurrentQueue<byte[]> _bufferPool; | private readonly ConcurrentQueue<byte[]> _bufferPool; | ||||
| private readonly SemaphoreSlim _queueLock; | private readonly SemaphoreSlim _queueLock; | ||||
| private readonly Logger _logger; | |||||
| private readonly ILogger _logger; | |||||
| private readonly int _ticksPerFrame, _queueLength; | private readonly int _ticksPerFrame, _queueLength; | ||||
| private bool _isPreloaded; | private bool _isPreloaded; | ||||
| private int _silenceFrames; | private int _silenceFrames; | ||||
| public BufferedWriteStream(AudioStream next, IAudioClient client, int bufferMillis, CancellationToken cancelToken, int maxFrameSize = 1500) | public BufferedWriteStream(AudioStream next, IAudioClient client, int bufferMillis, CancellationToken cancelToken, int maxFrameSize = 1500) | ||||
| : this(next, client as AudioClient, bufferMillis, cancelToken, null, maxFrameSize) { } | : this(next, client as AudioClient, bufferMillis, cancelToken, null, maxFrameSize) { } | ||||
| internal BufferedWriteStream(AudioStream next, AudioClient client, int bufferMillis, CancellationToken cancelToken, Logger logger, int maxFrameSize = 1500) | |||||
| internal BufferedWriteStream(AudioStream next, AudioClient client, int bufferMillis, CancellationToken cancelToken, ILogger logger, int maxFrameSize = 1500) | |||||
| { | { | ||||
| //maxFrameSize = 1275 was too limiting at 128kbps,2ch,60ms | //maxFrameSize = 1275 was too limiting at 128kbps,2ch,60ms | ||||
| _next = next; | _next = next; | ||||
| @@ -106,7 +107,7 @@ namespace Discord.Audio.Streams | |||||
| timestamp += OpusEncoder.FrameSamplesPerChannel; | timestamp += OpusEncoder.FrameSamplesPerChannel; | ||||
| _silenceFrames = 0; | _silenceFrames = 0; | ||||
| #if DEBUG | #if DEBUG | ||||
| var _ = _logger?.DebugAsync($"Sent {frame.Bytes} bytes ({_queuedFrames.Count} frames buffered)"); | |||||
| _logger?.LogDebug($"Sent {frame.Bytes} bytes ({_queuedFrames.Count} frames buffered)"); | |||||
| #endif | #endif | ||||
| } | } | ||||
| else | else | ||||
| @@ -125,7 +126,7 @@ namespace Discord.Audio.Streams | |||||
| timestamp += OpusEncoder.FrameSamplesPerChannel; | timestamp += OpusEncoder.FrameSamplesPerChannel; | ||||
| } | } | ||||
| #if DEBUG | #if DEBUG | ||||
| var _ = _logger?.DebugAsync("Buffer underrun"); | |||||
| _logger?.LogDebug("Buffer underrun"); | |||||
| #endif | #endif | ||||
| } | } | ||||
| } | } | ||||
| @@ -153,7 +154,7 @@ namespace Discord.Audio.Streams | |||||
| if (!_bufferPool.TryDequeue(out byte[] buffer)) | if (!_bufferPool.TryDequeue(out byte[] buffer)) | ||||
| { | { | ||||
| #if DEBUG | #if DEBUG | ||||
| var _ = _logger?.DebugAsync("Buffer overflow"); //Should never happen because of the queueLock | |||||
| _logger?.LogDebug("Buffer overflow"); //Should never happen because of the queueLock | |||||
| #endif | #endif | ||||
| #pragma warning disable IDISP016 | #pragma warning disable IDISP016 | ||||
| writeCancelToken?.Dispose(); | writeCancelToken?.Dispose(); | ||||
| @@ -165,7 +166,7 @@ namespace Discord.Audio.Streams | |||||
| if (!_isPreloaded && _queuedFrames.Count == _queueLength) | if (!_isPreloaded && _queuedFrames.Count == _queueLength) | ||||
| { | { | ||||
| #if DEBUG | #if DEBUG | ||||
| var _ = _logger?.DebugAsync("Preloaded"); | |||||
| _logger?.LogDebug("Preloaded"); | |||||
| #endif | #endif | ||||
| _isPreloaded = true; | _isPreloaded = true; | ||||
| } | } | ||||
| @@ -3,6 +3,7 @@ using System; | |||||
| using System.Threading; | using System.Threading; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Discord.Net; | using Discord.Net; | ||||
| using Microsoft.Extensions.Logging; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| @@ -14,7 +15,7 @@ namespace Discord | |||||
| private readonly AsyncEvent<Func<Exception, bool, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, bool, Task>>(); | private readonly AsyncEvent<Func<Exception, bool, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, bool, Task>>(); | ||||
| private readonly SemaphoreSlim _stateLock; | private readonly SemaphoreSlim _stateLock; | ||||
| private readonly Logger _logger; | |||||
| private readonly ILogger _logger; | |||||
| private readonly int _connectionTimeout; | private readonly int _connectionTimeout; | ||||
| private readonly Func<Task> _onConnecting; | private readonly Func<Task> _onConnecting; | ||||
| private readonly Func<Exception, Task> _onDisconnecting; | private readonly Func<Exception, Task> _onDisconnecting; | ||||
| @@ -28,7 +29,7 @@ namespace Discord | |||||
| public ConnectionState State { get; private set; } | public ConnectionState State { get; private set; } | ||||
| public CancellationToken CancelToken { get; private set; } | public CancellationToken CancelToken { get; private set; } | ||||
| internal ConnectionManager(SemaphoreSlim stateLock, Logger logger, int connectionTimeout, | |||||
| internal ConnectionManager(SemaphoreSlim stateLock, ILogger logger, int connectionTimeout, | |||||
| Func<Task> onConnecting, Func<Exception, Task> onDisconnecting, Action<Func<Exception, Task>> clientDisconnectHandler) | Func<Task> onConnecting, Func<Exception, Task> onDisconnecting, Action<Func<Exception, Task>> clientDisconnectHandler) | ||||
| { | { | ||||
| _stateLock = stateLock; | _stateLock = stateLock; | ||||
| @@ -85,12 +86,12 @@ namespace Discord | |||||
| Error(ex); //In case this exception didn't come from another Error call | Error(ex); //In case this exception didn't come from another Error call | ||||
| if (!reconnectCancelToken.IsCancellationRequested) | if (!reconnectCancelToken.IsCancellationRequested) | ||||
| { | { | ||||
| await _logger.WarningAsync(ex).ConfigureAwait(false); | |||||
| _logger.LogWarning(ex, ex.Message); | |||||
| await DisconnectAsync(ex, true).ConfigureAwait(false); | await DisconnectAsync(ex, true).ConfigureAwait(false); | ||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| await _logger.ErrorAsync(ex).ConfigureAwait(false); | |||||
| _logger.LogError(ex, ex.Message); | |||||
| await DisconnectAsync(ex, false).ConfigureAwait(false); | await DisconnectAsync(ex, false).ConfigureAwait(false); | ||||
| } | } | ||||
| } | } | ||||
| @@ -124,7 +125,7 @@ namespace Discord | |||||
| _connectionPromise = new TaskCompletionSource<bool>(); | _connectionPromise = new TaskCompletionSource<bool>(); | ||||
| State = ConnectionState.Connecting; | State = ConnectionState.Connecting; | ||||
| await _logger.InfoAsync("Connecting").ConfigureAwait(false); | |||||
| _logger.LogInformation("Connecting"); | |||||
| try | try | ||||
| { | { | ||||
| @@ -154,9 +155,9 @@ namespace Discord | |||||
| throw innerEx; | throw innerEx; | ||||
| } | } | ||||
| await _logger.InfoAsync("Connected").ConfigureAwait(false); | |||||
| _logger.LogInformation("Connected"); | |||||
| State = ConnectionState.Connected; | State = ConnectionState.Connected; | ||||
| await _logger.DebugAsync("Raising Event").ConfigureAwait(false); | |||||
| _logger.LogDebug("Raising Event"); | |||||
| await _connectedEvent.InvokeAsync().ConfigureAwait(false); | await _connectedEvent.InvokeAsync().ConfigureAwait(false); | ||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| @@ -169,13 +170,13 @@ namespace Discord | |||||
| { | { | ||||
| if (State == ConnectionState.Disconnected) return; | if (State == ConnectionState.Disconnected) return; | ||||
| State = ConnectionState.Disconnecting; | State = ConnectionState.Disconnecting; | ||||
| await _logger.InfoAsync("Disconnecting").ConfigureAwait(false); | |||||
| _logger.LogInformation("Disconnecting"); | |||||
| await _onDisconnecting(ex).ConfigureAwait(false); | await _onDisconnecting(ex).ConfigureAwait(false); | ||||
| await _disconnectedEvent.InvokeAsync(ex, isReconnecting).ConfigureAwait(false); | await _disconnectedEvent.InvokeAsync(ex, isReconnecting).ConfigureAwait(false); | ||||
| State = ConnectionState.Disconnected; | State = ConnectionState.Disconnected; | ||||
| await _logger.InfoAsync("Disconnected").ConfigureAwait(false); | |||||
| _logger.LogInformation("Disconnected"); | |||||
| } | } | ||||
| public async Task CompleteAsync() | public async Task CompleteAsync() | ||||
| @@ -328,7 +328,6 @@ namespace Discord.WebSocket | |||||
| private void RegisterEvents(DiscordSocketClient client, bool isPrimary) | private void RegisterEvents(DiscordSocketClient client, bool isPrimary) | ||||
| { | { | ||||
| client.Log += (msg) => _logEvent.InvokeAsync(msg); | |||||
| client.LoggedOut += () => | client.LoggedOut += () => | ||||
| { | { | ||||
| var state = LoginState; | var state = LoginState; | ||||
| @@ -5,6 +5,7 @@ using Discord.Net.Converters; | |||||
| using Discord.Net.Udp; | using Discord.Net.Udp; | ||||
| using Discord.Net.WebSockets; | using Discord.Net.WebSockets; | ||||
| using Discord.Rest; | using Discord.Rest; | ||||
| using Microsoft.Extensions.Logging; | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using Newtonsoft.Json.Linq; | using Newtonsoft.Json.Linq; | ||||
| using System; | using System; | ||||
| @@ -30,7 +31,7 @@ namespace Discord.WebSocket | |||||
| private readonly DiscordSocketClient _parentClient; | private readonly DiscordSocketClient _parentClient; | ||||
| private readonly ConcurrentQueue<long> _heartbeatTimes; | private readonly ConcurrentQueue<long> _heartbeatTimes; | ||||
| private readonly ConnectionManager _connection; | private readonly ConnectionManager _connection; | ||||
| private readonly Logger _gatewayLogger; | |||||
| private readonly ILogger _gatewayLogger; | |||||
| private readonly SemaphoreSlim _stateLock; | private readonly SemaphoreSlim _stateLock; | ||||
| private string _sessionId; | private string _sessionId; | ||||
| @@ -144,6 +145,7 @@ namespace Discord.WebSocket | |||||
| _gatewayIntents = config.GatewayIntents; | _gatewayIntents = config.GatewayIntents; | ||||
| _stateLock = new SemaphoreSlim(1, 1); | _stateLock = new SemaphoreSlim(1, 1); | ||||
| _gatewayLogger = LogManager.CreateLogger(ShardId == 0 && TotalShards == 1 ? "Gateway" : $"Shard #{ShardId}"); | _gatewayLogger = LogManager.CreateLogger(ShardId == 0 && TotalShards == 1 ? "Gateway" : $"Shard #{ShardId}"); | ||||
| _connection = new ConnectionManager(_stateLock, _gatewayLogger, config.ConnectionTimeout, | _connection = new ConnectionManager(_stateLock, _gatewayLogger, config.ConnectionTimeout, | ||||
| OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); | OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); | ||||
| @@ -157,18 +159,42 @@ namespace Discord.WebSocket | |||||
| _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | ||||
| _serializer.Error += (s, e) => | _serializer.Error += (s, e) => | ||||
| { | { | ||||
| _gatewayLogger.WarningAsync("Serializer Error", e.ErrorContext.Error).GetAwaiter().GetResult(); | |||||
| _gatewayLogger.LogWarning(e.ErrorContext.Error, "Serializer Error"); | |||||
| e.ErrorContext.Handled = true; | e.ErrorContext.Handled = true; | ||||
| }; | }; | ||||
| ApiClient.SentGatewayMessage += async opCode => await _gatewayLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); | |||||
| ApiClient.SentGatewayMessage += opCode => | |||||
| { | |||||
| _gatewayLogger.LogDebug($"Sent {opCode}"); | |||||
| return Task.CompletedTask; | |||||
| }; | |||||
| ApiClient.ReceivedGatewayEvent += ProcessMessageAsync; | ApiClient.ReceivedGatewayEvent += ProcessMessageAsync; | ||||
| LeftGuild += async g => await _gatewayLogger.InfoAsync($"Left {g.Name}").ConfigureAwait(false); | |||||
| JoinedGuild += async g => await _gatewayLogger.InfoAsync($"Joined {g.Name}").ConfigureAwait(false); | |||||
| GuildAvailable += async g => await _gatewayLogger.VerboseAsync($"Connected to {g.Name}").ConfigureAwait(false); | |||||
| GuildUnavailable += async g => await _gatewayLogger.VerboseAsync($"Disconnected from {g.Name}").ConfigureAwait(false); | |||||
| LatencyUpdated += async (old, val) => await _gatewayLogger.DebugAsync($"Latency = {val} ms").ConfigureAwait(false); | |||||
| LeftGuild += g => | |||||
| { | |||||
| _gatewayLogger.LogInformation($"Left {g.Name}"); | |||||
| return Task.CompletedTask; | |||||
| }; | |||||
| JoinedGuild += g => | |||||
| { | |||||
| _gatewayLogger.LogInformation($"Joined {g.Name}"); | |||||
| return Task.CompletedTask; | |||||
| }; | |||||
| GuildAvailable += g => | |||||
| { | |||||
| _gatewayLogger.LogTrace($"Connected to {g.Name}"); | |||||
| return Task.CompletedTask; | |||||
| }; | |||||
| GuildUnavailable += g => | |||||
| { | |||||
| _gatewayLogger.LogTrace($"Disconnected from {g.Name}"); | |||||
| return Task.CompletedTask; | |||||
| }; | |||||
| LatencyUpdated += (old, val) => | |||||
| { | |||||
| _gatewayLogger.LogDebug($"Latency = {val} ms"); | |||||
| return Task.CompletedTask; | |||||
| }; | |||||
| GuildAvailable += g => | GuildAvailable += g => | ||||
| { | { | ||||
| @@ -232,17 +258,17 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| try | try | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Connecting ApiClient").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Connecting ApiClient"); | |||||
| await ApiClient.ConnectAsync().ConfigureAwait(false); | await ApiClient.ConnectAsync().ConfigureAwait(false); | ||||
| if (_sessionId != null) | if (_sessionId != null) | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Resuming").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Resuming"); | |||||
| await ApiClient.SendResumeAsync(_sessionId, _lastSeq).ConfigureAwait(false); | await ApiClient.SendResumeAsync(_sessionId, _lastSeq).ConfigureAwait(false); | ||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Identifying").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Identifying"); | |||||
| await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, guildSubscriptions: _guildSubscriptions, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); | await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, guildSubscriptions: _guildSubscriptions, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); | ||||
| } | } | ||||
| } | } | ||||
| @@ -258,11 +284,11 @@ namespace Discord.WebSocket | |||||
| private async Task OnDisconnectingAsync(Exception ex) | private async Task OnDisconnectingAsync(Exception ex) | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Disconnecting ApiClient").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Disconnecting ApiClient"); | |||||
| await ApiClient.DisconnectAsync(ex).ConfigureAwait(false); | await ApiClient.DisconnectAsync(ex).ConfigureAwait(false); | ||||
| //Wait for tasks to complete | //Wait for tasks to complete | ||||
| await _gatewayLogger.DebugAsync("Waiting for heartbeater").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Waiting for heartbeater"); | |||||
| var heartbeatTask = _heartbeatTask; | var heartbeatTask = _heartbeatTask; | ||||
| if (heartbeatTask != null) | if (heartbeatTask != null) | ||||
| await heartbeatTask.ConfigureAwait(false); | await heartbeatTask.ConfigureAwait(false); | ||||
| @@ -271,18 +297,18 @@ namespace Discord.WebSocket | |||||
| while (_heartbeatTimes.TryDequeue(out _)) { } | while (_heartbeatTimes.TryDequeue(out _)) { } | ||||
| _lastMessageTime = 0; | _lastMessageTime = 0; | ||||
| await _gatewayLogger.DebugAsync("Waiting for guild downloader").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Waiting for guild downloader"); | |||||
| var guildDownloadTask = _guildDownloadTask; | var guildDownloadTask = _guildDownloadTask; | ||||
| if (guildDownloadTask != null) | if (guildDownloadTask != null) | ||||
| await guildDownloadTask.ConfigureAwait(false); | await guildDownloadTask.ConfigureAwait(false); | ||||
| _guildDownloadTask = null; | _guildDownloadTask = null; | ||||
| //Clear large guild queue | //Clear large guild queue | ||||
| await _gatewayLogger.DebugAsync("Clearing large guild queue").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Clearing large guild queue"); | |||||
| while (_largeGuilds.TryDequeue(out _)) { } | while (_largeGuilds.TryDequeue(out _)) { } | ||||
| //Raise virtual GUILD_UNAVAILABLEs | //Raise virtual GUILD_UNAVAILABLEs | ||||
| await _gatewayLogger.DebugAsync("Raising virtual GuildUnavailables").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Raising virtual GuildUnavailables"); | |||||
| foreach (var guild in State.Guilds) | foreach (var guild in State.Guilds) | ||||
| { | { | ||||
| if (guild.IsAvailable) | if (guild.IsAvailable) | ||||
| @@ -524,7 +550,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| case GatewayOpCode.Hello: | case GatewayOpCode.Hello: | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Hello").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Hello"); | |||||
| var data = (payload as JToken).ToObject<HelloEvent>(_serializer); | var data = (payload as JToken).ToObject<HelloEvent>(_serializer); | ||||
| _heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _connection.CancelToken); | _heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _connection.CancelToken); | ||||
| @@ -532,14 +558,14 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case GatewayOpCode.Heartbeat: | case GatewayOpCode.Heartbeat: | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Heartbeat").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Heartbeat"); | |||||
| await ApiClient.SendHeartbeatAsync(_lastSeq).ConfigureAwait(false); | await ApiClient.SendHeartbeatAsync(_lastSeq).ConfigureAwait(false); | ||||
| } | } | ||||
| break; | break; | ||||
| case GatewayOpCode.HeartbeatAck: | case GatewayOpCode.HeartbeatAck: | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received HeartbeatAck").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received HeartbeatAck"); | |||||
| if (_heartbeatTimes.TryDequeue(out long time)) | if (_heartbeatTimes.TryDequeue(out long time)) | ||||
| { | { | ||||
| @@ -553,8 +579,8 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case GatewayOpCode.InvalidSession: | case GatewayOpCode.InvalidSession: | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received InvalidSession").ConfigureAwait(false); | |||||
| await _gatewayLogger.WarningAsync("Failed to resume previous session").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received InvalidSession"); | |||||
| _gatewayLogger.LogWarning("Failed to resume previous session"); | |||||
| _sessionId = null; | _sessionId = null; | ||||
| _lastSeq = 0; | _lastSeq = 0; | ||||
| @@ -577,7 +603,7 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case GatewayOpCode.Reconnect: | case GatewayOpCode.Reconnect: | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Reconnect").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Reconnect"); | |||||
| _connection.Error(new GatewayReconnectException("Server requested a reconnect")); | _connection.Error(new GatewayReconnectException("Server requested a reconnect")); | ||||
| } | } | ||||
| break; | break; | ||||
| @@ -589,7 +615,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (READY)"); | |||||
| var data = (payload as JToken).ToObject<ReadyEvent>(_serializer); | var data = (payload as JToken).ToObject<ReadyEvent>(_serializer); | ||||
| var state = new ClientState(data.Guilds.Length, data.PrivateChannels.Length); | var state = new ClientState(data.Guilds.Length, data.PrivateChannels.Length); | ||||
| @@ -637,14 +663,14 @@ namespace Discord.WebSocket | |||||
| _ = DownloadUsersAsync(Guilds.Where(x => x.IsAvailable && !x.HasAllMembers)); | _ = DownloadUsersAsync(Guilds.Where(x => x.IsAvailable && !x.HasAllMembers)); | ||||
| await TimedInvokeAsync(_readyEvent, nameof(Ready)).ConfigureAwait(false); | await TimedInvokeAsync(_readyEvent, nameof(Ready)).ConfigureAwait(false); | ||||
| await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false); | |||||
| _gatewayLogger.LogInformation("Ready"); | |||||
| }); | }); | ||||
| _ = _connection.CompleteAsync(); | _ = _connection.CompleteAsync(); | ||||
| } | } | ||||
| break; | break; | ||||
| case "RESUMED": | case "RESUMED": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (RESUMED)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (RESUMED)"); | |||||
| _ = _connection.CompleteAsync(); | _ = _connection.CompleteAsync(); | ||||
| @@ -655,7 +681,7 @@ namespace Discord.WebSocket | |||||
| await GuildAvailableAsync(guild).ConfigureAwait(false); | await GuildAvailableAsync(guild).ConfigureAwait(false); | ||||
| } | } | ||||
| await _gatewayLogger.InfoAsync("Resumed previous session").ConfigureAwait(false); | |||||
| _gatewayLogger.LogInformation("Resumed previous session"); | |||||
| } | } | ||||
| break; | break; | ||||
| @@ -668,7 +694,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| type = "GUILD_AVAILABLE"; | type = "GUILD_AVAILABLE"; | ||||
| _lastGuildAvailableTime = Environment.TickCount; | _lastGuildAvailableTime = Environment.TickCount; | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_AVAILABLE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (GUILD_AVAILABLE)"); | |||||
| var guild = State.GetGuild(data.Id); | var guild = State.GetGuild(data.Id); | ||||
| if (guild != null) | if (guild != null) | ||||
| @@ -693,7 +719,7 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_CREATE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (GUILD_CREATE)"); | |||||
| var guild = AddGuild(data, State); | var guild = AddGuild(data, State); | ||||
| if (guild != null) | if (guild != null) | ||||
| @@ -711,7 +737,7 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case "GUILD_UPDATE": | case "GUILD_UPDATE": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_UPDATE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (GUILD_UPDATE)"); | |||||
| var data = (payload as JToken).ToObject<API.Guild>(_serializer); | var data = (payload as JToken).ToObject<API.Guild>(_serializer); | ||||
| var guild = State.GetGuild(data.Id); | var guild = State.GetGuild(data.Id); | ||||
| @@ -730,7 +756,7 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case "GUILD_EMOJIS_UPDATE": | case "GUILD_EMOJIS_UPDATE": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_EMOJIS_UPDATE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (GUILD_EMOJIS_UPDATE)"); | |||||
| var data = (payload as JToken).ToObject<API.Gateway.GuildEmojiUpdateEvent>(_serializer); | var data = (payload as JToken).ToObject<API.Gateway.GuildEmojiUpdateEvent>(_serializer); | ||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| @@ -749,7 +775,7 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case "GUILD_SYNC": | case "GUILD_SYNC": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_SYNC)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (GUILD_SYNC)"); | |||||
| var data = (payload as JToken).ToObject<GuildSyncEvent>(_serializer); | var data = (payload as JToken).ToObject<GuildSyncEvent>(_serializer); | ||||
| var guild = State.GetGuild(data.Id); | var guild = State.GetGuild(data.Id); | ||||
| if (guild != null) | if (guild != null) | ||||
| @@ -775,7 +801,7 @@ namespace Discord.WebSocket | |||||
| if (data.Unavailable == true) | if (data.Unavailable == true) | ||||
| { | { | ||||
| type = "GUILD_UNAVAILABLE"; | type = "GUILD_UNAVAILABLE"; | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_UNAVAILABLE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (GUILD_UNAVAILABLE)"); | |||||
| var guild = State.GetGuild(data.Id); | var guild = State.GetGuild(data.Id); | ||||
| if (guild != null) | if (guild != null) | ||||
| @@ -791,7 +817,7 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_DELETE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (GUILD_DELETE)"); | |||||
| var guild = RemoveGuild(data.Id); | var guild = RemoveGuild(data.Id); | ||||
| if (guild != null) | if (guild != null) | ||||
| @@ -812,7 +838,7 @@ namespace Discord.WebSocket | |||||
| //Channels | //Channels | ||||
| case "CHANNEL_CREATE": | case "CHANNEL_CREATE": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (CHANNEL_CREATE)"); | |||||
| var data = (payload as JToken).ToObject<API.Channel>(_serializer); | var data = (payload as JToken).ToObject<API.Channel>(_serializer); | ||||
| SocketChannel channel = null; | SocketChannel channel = null; | ||||
| @@ -849,7 +875,7 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case "CHANNEL_UPDATE": | case "CHANNEL_UPDATE": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_UPDATE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (CHANNEL_UPDATE)"); | |||||
| var data = (payload as JToken).ToObject<API.Channel>(_serializer); | var data = (payload as JToken).ToObject<API.Channel>(_serializer); | ||||
| var channel = State.GetChannel(data.Id); | var channel = State.GetChannel(data.Id); | ||||
| @@ -876,7 +902,7 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case "CHANNEL_DELETE": | case "CHANNEL_DELETE": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_DELETE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (CHANNEL_DELETE)"); | |||||
| SocketChannel channel = null; | SocketChannel channel = null; | ||||
| var data = (payload as JToken).ToObject<API.Channel>(_serializer); | var data = (payload as JToken).ToObject<API.Channel>(_serializer); | ||||
| @@ -915,7 +941,7 @@ namespace Discord.WebSocket | |||||
| //Members | //Members | ||||
| case "GUILD_MEMBER_ADD": | case "GUILD_MEMBER_ADD": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_ADD)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (GUILD_MEMBER_ADD)"); | |||||
| var data = (payload as JToken).ToObject<GuildMemberAddEvent>(_serializer); | var data = (payload as JToken).ToObject<GuildMemberAddEvent>(_serializer); | ||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| @@ -941,7 +967,7 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case "GUILD_MEMBER_UPDATE": | case "GUILD_MEMBER_UPDATE": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_UPDATE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (GUILD_MEMBER_UPDATE)"); | |||||
| var data = (payload as JToken).ToObject<GuildMemberUpdateEvent>(_serializer); | var data = (payload as JToken).ToObject<GuildMemberUpdateEvent>(_serializer); | ||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| @@ -986,7 +1012,7 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case "GUILD_MEMBER_REMOVE": | case "GUILD_MEMBER_REMOVE": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_REMOVE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (GUILD_MEMBER_REMOVE)"); | |||||
| var data = (payload as JToken).ToObject<GuildMemberRemoveEvent>(_serializer); | var data = (payload as JToken).ToObject<GuildMemberRemoveEvent>(_serializer); | ||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| @@ -1021,7 +1047,7 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case "GUILD_MEMBERS_CHUNK": | case "GUILD_MEMBERS_CHUNK": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBERS_CHUNK)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (GUILD_MEMBERS_CHUNK)"); | |||||
| var data = (payload as JToken).ToObject<GuildMembersChunkEvent>(_serializer); | var data = (payload as JToken).ToObject<GuildMembersChunkEvent>(_serializer); | ||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| @@ -1045,7 +1071,7 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case "CHANNEL_RECIPIENT_ADD": | case "CHANNEL_RECIPIENT_ADD": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_ADD)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (CHANNEL_RECIPIENT_ADD)"); | |||||
| var data = (payload as JToken).ToObject<RecipientEvent>(_serializer); | var data = (payload as JToken).ToObject<RecipientEvent>(_serializer); | ||||
| if (State.GetChannel(data.ChannelId) is SocketGroupChannel channel) | if (State.GetChannel(data.ChannelId) is SocketGroupChannel channel) | ||||
| @@ -1062,7 +1088,7 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case "CHANNEL_RECIPIENT_REMOVE": | case "CHANNEL_RECIPIENT_REMOVE": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_REMOVE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (CHANNEL_RECIPIENT_REMOVE)"); | |||||
| var data = (payload as JToken).ToObject<RecipientEvent>(_serializer); | var data = (payload as JToken).ToObject<RecipientEvent>(_serializer); | ||||
| if (State.GetChannel(data.ChannelId) is SocketGroupChannel channel) | if (State.GetChannel(data.ChannelId) is SocketGroupChannel channel) | ||||
| @@ -1087,7 +1113,7 @@ namespace Discord.WebSocket | |||||
| //Roles | //Roles | ||||
| case "GUILD_ROLE_CREATE": | case "GUILD_ROLE_CREATE": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_CREATE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (GUILD_ROLE_CREATE)"); | |||||
| var data = (payload as JToken).ToObject<GuildRoleCreateEvent>(_serializer); | var data = (payload as JToken).ToObject<GuildRoleCreateEvent>(_serializer); | ||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| @@ -1111,7 +1137,7 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case "GUILD_ROLE_UPDATE": | case "GUILD_ROLE_UPDATE": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_UPDATE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (GUILD_ROLE_UPDATE)"); | |||||
| var data = (payload as JToken).ToObject<GuildRoleUpdateEvent>(_serializer); | var data = (payload as JToken).ToObject<GuildRoleUpdateEvent>(_serializer); | ||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| @@ -1146,7 +1172,7 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case "GUILD_ROLE_DELETE": | case "GUILD_ROLE_DELETE": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_DELETE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (GUILD_ROLE_DELETE)"); | |||||
| var data = (payload as JToken).ToObject<GuildRoleDeleteEvent>(_serializer); | var data = (payload as JToken).ToObject<GuildRoleDeleteEvent>(_serializer); | ||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| @@ -1180,7 +1206,7 @@ namespace Discord.WebSocket | |||||
| //Bans | //Bans | ||||
| case "GUILD_BAN_ADD": | case "GUILD_BAN_ADD": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_ADD)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (GUILD_BAN_ADD)"); | |||||
| var data = (payload as JToken).ToObject<GuildBanEvent>(_serializer); | var data = (payload as JToken).ToObject<GuildBanEvent>(_serializer); | ||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| @@ -1206,7 +1232,7 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case "GUILD_BAN_REMOVE": | case "GUILD_BAN_REMOVE": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_REMOVE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (GUILD_BAN_REMOVE)"); | |||||
| var data = (payload as JToken).ToObject<GuildBanEvent>(_serializer); | var data = (payload as JToken).ToObject<GuildBanEvent>(_serializer); | ||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| @@ -1234,7 +1260,7 @@ namespace Discord.WebSocket | |||||
| //Messages | //Messages | ||||
| case "MESSAGE_CREATE": | case "MESSAGE_CREATE": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (MESSAGE_CREATE)"); | |||||
| var data = (payload as JToken).ToObject<API.Message>(_serializer); | var data = (payload as JToken).ToObject<API.Message>(_serializer); | ||||
| if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | ||||
| @@ -1291,7 +1317,7 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case "MESSAGE_UPDATE": | case "MESSAGE_UPDATE": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (MESSAGE_UPDATE)"); | |||||
| var data = (payload as JToken).ToObject<API.Message>(_serializer); | var data = (payload as JToken).ToObject<API.Message>(_serializer); | ||||
| if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | ||||
| @@ -1344,7 +1370,7 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case "MESSAGE_DELETE": | case "MESSAGE_DELETE": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (MESSAGE_DELETE)"); | |||||
| var data = (payload as JToken).ToObject<API.Message>(_serializer); | var data = (payload as JToken).ToObject<API.Message>(_serializer); | ||||
| if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | ||||
| @@ -1371,7 +1397,7 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case "MESSAGE_REACTION_ADD": | case "MESSAGE_REACTION_ADD": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_ADD)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (MESSAGE_REACTION_ADD)"); | |||||
| var data = (payload as JToken).ToObject<API.Gateway.Reaction>(_serializer); | var data = (payload as JToken).ToObject<API.Gateway.Reaction>(_serializer); | ||||
| if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | ||||
| @@ -1412,7 +1438,7 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case "MESSAGE_REACTION_REMOVE": | case "MESSAGE_REACTION_REMOVE": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (MESSAGE_REACTION_REMOVE)"); | |||||
| var data = (payload as JToken).ToObject<API.Gateway.Reaction>(_serializer); | var data = (payload as JToken).ToObject<API.Gateway.Reaction>(_serializer); | ||||
| if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | ||||
| @@ -1445,7 +1471,7 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case "MESSAGE_REACTION_REMOVE_ALL": | case "MESSAGE_REACTION_REMOVE_ALL": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_ALL)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (MESSAGE_REACTION_REMOVE_ALL)"); | |||||
| var data = (payload as JToken).ToObject<RemoveAllReactionsEvent>(_serializer); | var data = (payload as JToken).ToObject<RemoveAllReactionsEvent>(_serializer); | ||||
| if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | ||||
| @@ -1467,7 +1493,7 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case "MESSAGE_REACTION_REMOVE_EMOJI": | case "MESSAGE_REACTION_REMOVE_EMOJI": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_EMOJI)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (MESSAGE_REACTION_REMOVE_EMOJI)"); | |||||
| var data = (payload as JToken).ToObject<API.Gateway.RemoveAllReactionsForEmoteEvent>(_serializer); | var data = (payload as JToken).ToObject<API.Gateway.RemoveAllReactionsForEmoteEvent>(_serializer); | ||||
| if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | ||||
| @@ -1495,11 +1521,11 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case "MESSAGE_DELETE_BULK": | case "MESSAGE_DELETE_BULK": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (MESSAGE_DELETE_BULK)"); | |||||
| if (!ExclusiveBulkDelete.HasValue) | if (!ExclusiveBulkDelete.HasValue) | ||||
| { | { | ||||
| await _gatewayLogger.WarningAsync("A bulk delete event has been received, but the event handling behavior has not been set. " + | |||||
| _gatewayLogger.LogWarning("A bulk delete event has been received, but the event handling behavior has not been set. " + | |||||
| "To suppress this message, set the ExclusiveBulkDelete configuration property. " + | "To suppress this message, set the ExclusiveBulkDelete configuration property. " + | ||||
| "This message will appear only once."); | "This message will appear only once."); | ||||
| ExclusiveBulkDelete = false; | ExclusiveBulkDelete = false; | ||||
| @@ -1540,7 +1566,7 @@ namespace Discord.WebSocket | |||||
| //Statuses | //Statuses | ||||
| case "PRESENCE_UPDATE": | case "PRESENCE_UPDATE": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (PRESENCE_UPDATE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (PRESENCE_UPDATE)"); | |||||
| var data = (payload as JToken).ToObject<API.Presence>(_serializer); | var data = (payload as JToken).ToObject<API.Presence>(_serializer); | ||||
| @@ -1599,7 +1625,7 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case "TYPING_START": | case "TYPING_START": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (TYPING_START)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (TYPING_START)"); | |||||
| var data = (payload as JToken).ToObject<TypingStartEvent>(_serializer); | var data = (payload as JToken).ToObject<TypingStartEvent>(_serializer); | ||||
| if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | ||||
| @@ -1626,7 +1652,7 @@ namespace Discord.WebSocket | |||||
| //Users | //Users | ||||
| case "USER_UPDATE": | case "USER_UPDATE": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (USER_UPDATE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (USER_UPDATE)"); | |||||
| var data = (payload as JToken).ToObject<API.User>(_serializer); | var data = (payload as JToken).ToObject<API.User>(_serializer); | ||||
| if (data.Id == CurrentUser.Id) | if (data.Id == CurrentUser.Id) | ||||
| @@ -1637,7 +1663,7 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| await _gatewayLogger.WarningAsync("Received USER_UPDATE for wrong user.").ConfigureAwait(false); | |||||
| _gatewayLogger.LogWarning("Received USER_UPDATE for wrong user."); | |||||
| return; | return; | ||||
| } | } | ||||
| } | } | ||||
| @@ -1646,7 +1672,7 @@ namespace Discord.WebSocket | |||||
| //Voice | //Voice | ||||
| case "VOICE_STATE_UPDATE": | case "VOICE_STATE_UPDATE": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_STATE_UPDATE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (VOICE_STATE_UPDATE)"); | |||||
| var data = (payload as JToken).ToObject<API.VoiceState>(_serializer); | var data = (payload as JToken).ToObject<API.VoiceState>(_serializer); | ||||
| SocketUser user; | SocketUser user; | ||||
| @@ -1720,7 +1746,7 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case "VOICE_SERVER_UPDATE": | case "VOICE_SERVER_UPDATE": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_SERVER_UPDATE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (VOICE_SERVER_UPDATE)"); | |||||
| var data = (payload as JToken).ToObject<VoiceServerUpdateEvent>(_serializer); | var data = (payload as JToken).ToObject<VoiceServerUpdateEvent>(_serializer); | ||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| @@ -1753,7 +1779,7 @@ namespace Discord.WebSocket | |||||
| //Invites | //Invites | ||||
| case "INVITE_CREATE": | case "INVITE_CREATE": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (INVITE_CREATE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (INVITE_CREATE)"); | |||||
| var data = (payload as JToken).ToObject<API.Gateway.InviteCreateEvent>(_serializer); | var data = (payload as JToken).ToObject<API.Gateway.InviteCreateEvent>(_serializer); | ||||
| if (State.GetChannel(data.ChannelId) is SocketGuildChannel channel) | if (State.GetChannel(data.ChannelId) is SocketGuildChannel channel) | ||||
| @@ -1786,7 +1812,7 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case "INVITE_DELETE": | case "INVITE_DELETE": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (INVITE_DELETE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Received Dispatch (INVITE_DELETE)"); | |||||
| var data = (payload as JToken).ToObject<API.Gateway.InviteDeleteEvent>(_serializer); | var data = (payload as JToken).ToObject<API.Gateway.InviteDeleteEvent>(_serializer); | ||||
| if (State.GetChannel(data.ChannelId) is SocketGuildChannel channel) | if (State.GetChannel(data.ChannelId) is SocketGuildChannel channel) | ||||
| @@ -1810,41 +1836,41 @@ namespace Discord.WebSocket | |||||
| //Ignored (User only) | //Ignored (User only) | ||||
| case "CHANNEL_PINS_ACK": | case "CHANNEL_PINS_ACK": | ||||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Ignored Dispatch (CHANNEL_PINS_ACK)"); | |||||
| break; | break; | ||||
| case "CHANNEL_PINS_UPDATE": | case "CHANNEL_PINS_UPDATE": | ||||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_UPDATE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Ignored Dispatch (CHANNEL_PINS_UPDATE)"); | |||||
| break; | break; | ||||
| case "GUILD_INTEGRATIONS_UPDATE": | case "GUILD_INTEGRATIONS_UPDATE": | ||||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (GUILD_INTEGRATIONS_UPDATE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Ignored Dispatch (GUILD_INTEGRATIONS_UPDATE)"); | |||||
| break; | break; | ||||
| case "MESSAGE_ACK": | case "MESSAGE_ACK": | ||||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (MESSAGE_ACK)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Ignored Dispatch (MESSAGE_ACK)"); | |||||
| break; | break; | ||||
| case "PRESENCES_REPLACE": | case "PRESENCES_REPLACE": | ||||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (PRESENCES_REPLACE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Ignored Dispatch (PRESENCES_REPLACE)"); | |||||
| break; | break; | ||||
| case "USER_SETTINGS_UPDATE": | case "USER_SETTINGS_UPDATE": | ||||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (USER_SETTINGS_UPDATE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Ignored Dispatch (USER_SETTINGS_UPDATE)"); | |||||
| break; | break; | ||||
| case "WEBHOOKS_UPDATE": | case "WEBHOOKS_UPDATE": | ||||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (WEBHOOKS_UPDATE)").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Ignored Dispatch (WEBHOOKS_UPDATE)"); | |||||
| break; | break; | ||||
| //Others | //Others | ||||
| default: | default: | ||||
| await _gatewayLogger.WarningAsync($"Unknown Dispatch ({type})").ConfigureAwait(false); | |||||
| _gatewayLogger.LogWarning($"Unknown Dispatch ({type})"); | |||||
| break; | break; | ||||
| } | } | ||||
| break; | break; | ||||
| default: | default: | ||||
| await _gatewayLogger.WarningAsync($"Unknown OpCode ({opCode})").ConfigureAwait(false); | |||||
| _gatewayLogger.LogWarning($"Unknown OpCode ({opCode})"); | |||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| { | { | ||||
| await _gatewayLogger.ErrorAsync($"Error handling {opCode}{(type != null ? $" ({type})" : "")}", ex).ConfigureAwait(false); | |||||
| _gatewayLogger.LogError(ex, $"Error handling {opCode}{(type != null ? $" ({type})" : "")}"); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1852,7 +1878,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Heartbeat Started").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Heartbeat Started"); | |||||
| while (!cancelToken.IsCancellationRequested) | while (!cancelToken.IsCancellationRequested) | ||||
| { | { | ||||
| int now = Environment.TickCount; | int now = Environment.TickCount; | ||||
| @@ -1874,20 +1900,20 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| { | { | ||||
| await _gatewayLogger.WarningAsync("Heartbeat Errored", ex).ConfigureAwait(false); | |||||
| _gatewayLogger.LogWarning("Heartbeat Errored", ex); | |||||
| } | } | ||||
| await Task.Delay(intervalMillis, cancelToken).ConfigureAwait(false); | await Task.Delay(intervalMillis, cancelToken).ConfigureAwait(false); | ||||
| } | } | ||||
| await _gatewayLogger.DebugAsync("Heartbeat Stopped").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Heartbeat Stopped"); | |||||
| } | } | ||||
| catch (OperationCanceledException) | catch (OperationCanceledException) | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Heartbeat Stopped").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug("Heartbeat Stopped"); | |||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| { | { | ||||
| await _gatewayLogger.ErrorAsync("Heartbeat Errored", ex).ConfigureAwait(false); | |||||
| _gatewayLogger.LogError(ex, "Heartbeat Errored"); | |||||
| } | } | ||||
| } | } | ||||
| /*public async Task WaitForGuildsAsync() | /*public async Task WaitForGuildsAsync() | ||||
| @@ -1896,23 +1922,23 @@ namespace Discord.WebSocket | |||||
| if (downloadTask != null) | if (downloadTask != null) | ||||
| await _guildDownloadTask.ConfigureAwait(false); | await _guildDownloadTask.ConfigureAwait(false); | ||||
| }*/ | }*/ | ||||
| private async Task WaitForGuildsAsync(CancellationToken cancelToken, Logger logger) | |||||
| private async Task WaitForGuildsAsync(CancellationToken cancelToken, ILogger logger) | |||||
| { | { | ||||
| //Wait for GUILD_AVAILABLEs | //Wait for GUILD_AVAILABLEs | ||||
| try | try | ||||
| { | { | ||||
| await logger.DebugAsync("GuildDownloader Started").ConfigureAwait(false); | |||||
| logger.LogDebug("GuildDownloader Started"); | |||||
| while ((_unavailableGuildCount != 0) && (Environment.TickCount - _lastGuildAvailableTime < BaseConfig.MaxWaitBetweenGuildAvailablesBeforeReady)) | while ((_unavailableGuildCount != 0) && (Environment.TickCount - _lastGuildAvailableTime < BaseConfig.MaxWaitBetweenGuildAvailablesBeforeReady)) | ||||
| await Task.Delay(500, cancelToken).ConfigureAwait(false); | await Task.Delay(500, cancelToken).ConfigureAwait(false); | ||||
| await logger.DebugAsync("GuildDownloader Stopped").ConfigureAwait(false); | |||||
| logger.LogDebug("GuildDownloader Stopped"); | |||||
| } | } | ||||
| catch (OperationCanceledException) | catch (OperationCanceledException) | ||||
| { | { | ||||
| await logger.DebugAsync("GuildDownloader Stopped").ConfigureAwait(false); | |||||
| logger.LogDebug("GuildDownloader Stopped"); | |||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| { | { | ||||
| await logger.ErrorAsync("GuildDownloader Errored", ex).ConfigureAwait(false); | |||||
| logger.LogError(ex, "GuildDownloader Errored"); | |||||
| } | } | ||||
| } | } | ||||
| private async Task SyncGuildsAsync() | private async Task SyncGuildsAsync() | ||||
| @@ -2042,40 +2068,45 @@ namespace Discord.WebSocket | |||||
| var handlersTask = action(); | var handlersTask = action(); | ||||
| if (await Task.WhenAny(timeoutTask, handlersTask).ConfigureAwait(false) == timeoutTask) | if (await Task.WhenAny(timeoutTask, handlersTask).ConfigureAwait(false) == timeoutTask) | ||||
| { | { | ||||
| await _gatewayLogger.WarningAsync($"A {name} handler is blocking the gateway task.").ConfigureAwait(false); | |||||
| _gatewayLogger.LogWarning($"A {name} handler is blocking the gateway task."); | |||||
| } | } | ||||
| await handlersTask.ConfigureAwait(false); //Ensure the handler completes | await handlersTask.ConfigureAwait(false); //Ensure the handler completes | ||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| { | { | ||||
| await _gatewayLogger.WarningAsync($"A {name} handler has thrown an unhandled exception.", ex).ConfigureAwait(false); | |||||
| _gatewayLogger.LogWarning(ex, $"A {name} handler has thrown an unhandled exception."); | |||||
| } | } | ||||
| } | } | ||||
| private async Task UnknownGlobalUserAsync(string evnt, ulong userId) | |||||
| private Task UnknownGlobalUserAsync(string evnt, ulong userId) | |||||
| { | { | ||||
| string details = $"{evnt} User={userId}"; | string details = $"{evnt} User={userId}"; | ||||
| await _gatewayLogger.WarningAsync($"Unknown User ({details}).").ConfigureAwait(false); | |||||
| _gatewayLogger.LogWarning($"Unknown User ({details})."); | |||||
| return Task.CompletedTask; | |||||
| } | } | ||||
| private async Task UnknownChannelUserAsync(string evnt, ulong userId, ulong channelId) | |||||
| private Task UnknownChannelUserAsync(string evnt, ulong userId, ulong channelId) | |||||
| { | { | ||||
| string details = $"{evnt} User={userId} Channel={channelId}"; | string details = $"{evnt} User={userId} Channel={channelId}"; | ||||
| await _gatewayLogger.WarningAsync($"Unknown User ({details}).").ConfigureAwait(false); | |||||
| _gatewayLogger.LogWarning($"Unknown User ({details})."); | |||||
| return Task.CompletedTask; | |||||
| } | } | ||||
| private async Task UnknownGuildUserAsync(string evnt, ulong userId, ulong guildId) | |||||
| private Task UnknownGuildUserAsync(string evnt, ulong userId, ulong guildId) | |||||
| { | { | ||||
| string details = $"{evnt} User={userId} Guild={guildId}"; | string details = $"{evnt} User={userId} Guild={guildId}"; | ||||
| await _gatewayLogger.WarningAsync($"Unknown User ({details}).").ConfigureAwait(false); | |||||
| _gatewayLogger.LogWarning($"Unknown User ({details})."); | |||||
| return Task.CompletedTask; | |||||
| } | } | ||||
| private async Task IncompleteGuildUserAsync(string evnt, ulong userId, ulong guildId) | |||||
| private Task IncompleteGuildUserAsync(string evnt, ulong userId, ulong guildId) | |||||
| { | { | ||||
| string details = $"{evnt} User={userId} Guild={guildId}"; | string details = $"{evnt} User={userId} Guild={guildId}"; | ||||
| await _gatewayLogger.DebugAsync($"User has not been downloaded ({details}).").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug($"User has not been downloaded ({details})."); | |||||
| return Task.CompletedTask; | |||||
| } | } | ||||
| private async Task UnknownChannelAsync(string evnt, ulong channelId) | |||||
| private Task UnknownChannelAsync(string evnt, ulong channelId) | |||||
| { | { | ||||
| string details = $"{evnt} Channel={channelId}"; | string details = $"{evnt} Channel={channelId}"; | ||||
| await _gatewayLogger.WarningAsync($"Unknown Channel ({details}).").ConfigureAwait(false); | |||||
| _gatewayLogger.LogWarning($"Unknown Channel ({details})."); | |||||
| return Task.CompletedTask; | |||||
| } | } | ||||
| private async Task UnknownChannelAsync(string evnt, ulong channelId, ulong guildId) | private async Task UnknownChannelAsync(string evnt, ulong channelId, ulong guildId) | ||||
| { | { | ||||
| @@ -2085,22 +2116,25 @@ namespace Discord.WebSocket | |||||
| return; | return; | ||||
| } | } | ||||
| string details = $"{evnt} Channel={channelId} Guild={guildId}"; | string details = $"{evnt} Channel={channelId} Guild={guildId}"; | ||||
| await _gatewayLogger.WarningAsync($"Unknown Channel ({details}).").ConfigureAwait(false); | |||||
| _gatewayLogger.LogWarning($"Unknown Channel ({details})."); | |||||
| } | } | ||||
| private async Task UnknownRoleAsync(string evnt, ulong roleId, ulong guildId) | |||||
| private Task UnknownRoleAsync(string evnt, ulong roleId, ulong guildId) | |||||
| { | { | ||||
| string details = $"{evnt} Role={roleId} Guild={guildId}"; | string details = $"{evnt} Role={roleId} Guild={guildId}"; | ||||
| await _gatewayLogger.WarningAsync($"Unknown Role ({details}).").ConfigureAwait(false); | |||||
| _gatewayLogger.LogWarning($"Unknown Role ({details})."); | |||||
| return Task.CompletedTask; | |||||
| } | } | ||||
| private async Task UnknownGuildAsync(string evnt, ulong guildId) | |||||
| private Task UnknownGuildAsync(string evnt, ulong guildId) | |||||
| { | { | ||||
| string details = $"{evnt} Guild={guildId}"; | string details = $"{evnt} Guild={guildId}"; | ||||
| await _gatewayLogger.WarningAsync($"Unknown Guild ({details}).").ConfigureAwait(false); | |||||
| _gatewayLogger.LogWarning($"Unknown Guild ({details})."); | |||||
| return Task.CompletedTask; | |||||
| } | } | ||||
| private async Task UnsyncedGuildAsync(string evnt, ulong guildId) | |||||
| private Task UnsyncedGuildAsync(string evnt, ulong guildId) | |||||
| { | { | ||||
| string details = $"{evnt} Guild={guildId}"; | string details = $"{evnt} Guild={guildId}"; | ||||
| await _gatewayLogger.DebugAsync($"Unsynced Guild ({details}).").ConfigureAwait(false); | |||||
| _gatewayLogger.LogDebug($"Unsynced Guild ({details})."); | |||||
| return Task.CompletedTask; | |||||
| } | } | ||||
| internal int GetAudioId() => _nextAudioId++; | internal int GetAudioId() => _nextAudioId++; | ||||
| @@ -6,21 +6,19 @@ using System.Text.RegularExpressions; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Discord.Logging; | using Discord.Logging; | ||||
| using Discord.Rest; | using Discord.Rest; | ||||
| using Microsoft.Extensions.Logging; | |||||
| namespace Discord.Webhook | namespace Discord.Webhook | ||||
| { | { | ||||
| /// <summary> A client responsible for connecting as a Webhook. </summary> | /// <summary> A client responsible for connecting as a Webhook. </summary> | ||||
| public class DiscordWebhookClient : IDisposable | public class DiscordWebhookClient : IDisposable | ||||
| { | { | ||||
| public event Func<LogMessage, Task> Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } } | |||||
| internal readonly AsyncEvent<Func<LogMessage, Task>> _logEvent = new AsyncEvent<Func<LogMessage, Task>>(); | |||||
| private readonly ulong _webhookId; | private readonly ulong _webhookId; | ||||
| internal IWebhook Webhook; | internal IWebhook Webhook; | ||||
| internal readonly Logger _restLogger; | |||||
| internal readonly ILogger _restLogger; | |||||
| internal API.DiscordRestApiClient ApiClient { get; } | internal API.DiscordRestApiClient ApiClient { get; } | ||||
| internal LogManager LogManager { get; } | |||||
| internal ILoggerFactory LogManager { get; } | |||||
| /// <summary> Creates a new Webhook Discord client. </summary> | /// <summary> Creates a new Webhook Discord client. </summary> | ||||
| public DiscordWebhookClient(IWebhook webhook) | public DiscordWebhookClient(IWebhook webhook) | ||||
| @@ -69,19 +67,31 @@ namespace Discord.Webhook | |||||
| private DiscordWebhookClient(DiscordRestConfig config) | private DiscordWebhookClient(DiscordRestConfig config) | ||||
| { | { | ||||
| ApiClient = CreateApiClient(config); | ApiClient = CreateApiClient(config); | ||||
| LogManager = new LogManager(config.LogLevel); | |||||
| LogManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false); | |||||
| LogManager = config.LoggerFactory; | |||||
| if (LogManager == null) | |||||
| { | |||||
| LogManager = new DefaultLoggerFactory(); | |||||
| LogManager.AddProvider(new DefaultLoggerProvider()); | |||||
| } | |||||
| _restLogger = LogManager.CreateLogger("Rest"); | _restLogger = LogManager.CreateLogger("Rest"); | ||||
| ApiClient.RequestQueue.RateLimitTriggered += async (id, info, endpoint) => | |||||
| ApiClient.RequestQueue.RateLimitTriggered += (id, info, endpoint) => | |||||
| { | { | ||||
| if (info == null) | if (info == null) | ||||
| await _restLogger.VerboseAsync($"Preemptive Rate limit triggered: {endpoint} {(id.IsHashBucket ? $"(Bucket: {id.BucketHash})" : "")}").ConfigureAwait(false); | |||||
| _restLogger.LogTrace($"Preemptive Rate limit triggered: {endpoint} {(id.IsHashBucket ? $"(Bucket: {id.BucketHash})" : "")}"); | |||||
| else | else | ||||
| await _restLogger.WarningAsync($"Rate limit triggered: {endpoint} {(id.IsHashBucket ? $"(Bucket: {id.BucketHash})" : "")}").ConfigureAwait(false); | |||||
| _restLogger.LogWarning($"Rate limit triggered: {endpoint} {(id.IsHashBucket ? $"(Bucket: {id.BucketHash})" : "")}"); | |||||
| return Task.CompletedTask; | |||||
| }; | |||||
| ApiClient.SentRequest += (method, endpoint, millis) => | |||||
| { | |||||
| _restLogger.LogTrace($"{method} {endpoint}: {millis} ms"); | |||||
| return Task.CompletedTask; | |||||
| }; | }; | ||||
| ApiClient.SentRequest += async (method, endpoint, millis) => await _restLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false); | |||||
| } | } | ||||
| private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) | private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) | ||||
| => new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent); | => new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent); | ||||
| @@ -22,12 +22,6 @@ namespace Discord | |||||
| this.output = output; | this.output = output; | ||||
| output.WriteLine($"RestGuildFixture using guild: {guild.Id}"); | output.WriteLine($"RestGuildFixture using guild: {guild.Id}"); | ||||
| // capture all console output | // capture all console output | ||||
| guildFixture.Client.Log += LogAsync; | |||||
| } | |||||
| private Task LogAsync(LogMessage message) | |||||
| { | |||||
| output.WriteLine(message.ToString()); | |||||
| return Task.CompletedTask; | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| @@ -19,7 +19,6 @@ namespace Discord | |||||
| throw new Exception("The DNET_TEST_TOKEN environment variable was not provided."); | throw new Exception("The DNET_TEST_TOKEN environment variable was not provided."); | ||||
| Client = new DiscordRestClient(new DiscordRestConfig() | Client = new DiscordRestClient(new DiscordRestConfig() | ||||
| { | { | ||||
| LogLevel = LogSeverity.Debug, | |||||
| DefaultRetryMode = RetryMode.AlwaysRetry | DefaultRetryMode = RetryMode.AlwaysRetry | ||||
| }); | }); | ||||
| Client.LoginAsync(TokenType.Bot, token).Wait(); | Client.LoginAsync(TokenType.Bot, token).Wait(); | ||||
| @@ -20,12 +20,6 @@ namespace Discord | |||||
| guild = guildFixture.Guild; | guild = guildFixture.Guild; | ||||
| this.output = output; | this.output = output; | ||||
| output.WriteLine($"RestGuildFixture using guild: {guild.Id}"); | output.WriteLine($"RestGuildFixture using guild: {guild.Id}"); | ||||
| guildFixture.Client.Log += LogAsync; | |||||
| } | |||||
| private Task LogAsync(LogMessage message) | |||||
| { | |||||
| output.WriteLine(message.ToString()); | |||||
| return Task.CompletedTask; | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// Ensures that the CurrentUser is the owner of the guild. | /// Ensures that the CurrentUser is the owner of the guild. | ||||