| @@ -0,0 +1,15 @@ | |||||
| using System; | |||||
| namespace Discord.Commands | |||||
| { | |||||
| public class CommandException : Exception | |||||
| { | |||||
| public CommandInfo Command { get; } | |||||
| public ICommandContext Content { get; } | |||||
| public CommandException(CommandInfo command, ICommandContext context, Exception ex) | |||||
| : base($"Error occurred executing {command.GetLogText(context)}.", ex) | |||||
| { | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,4 +1,6 @@ | |||||
| using System; | |||||
| using Discord.Commands.Builders; | |||||
| using Discord.Logging; | |||||
| using System; | |||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| @@ -7,12 +9,13 @@ using System.Reflection; | |||||
| using System.Threading; | using System.Threading; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Discord.Commands.Builders; | |||||
| namespace Discord.Commands | namespace Discord.Commands | ||||
| { | { | ||||
| public class CommandService | public class CommandService | ||||
| { | { | ||||
| 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 SemaphoreSlim _moduleLock; | private readonly SemaphoreSlim _moduleLock; | ||||
| private readonly ConcurrentDictionary<Type, ModuleInfo> _typedModuleDefs; | private readonly ConcurrentDictionary<Type, ModuleInfo> _typedModuleDefs; | ||||
| private readonly ConcurrentDictionary<Type, ConcurrentDictionary<Type, TypeReader>> _typeReaders; | private readonly ConcurrentDictionary<Type, ConcurrentDictionary<Type, TypeReader>> _typeReaders; | ||||
| @@ -24,6 +27,8 @@ namespace Discord.Commands | |||||
| internal readonly bool _caseSensitive; | internal readonly bool _caseSensitive; | ||||
| internal readonly char _separatorChar; | internal readonly char _separatorChar; | ||||
| internal readonly RunMode _defaultRunMode; | internal readonly RunMode _defaultRunMode; | ||||
| internal readonly Logger _cmdLogger; | |||||
| internal readonly LogManager _logManager; | |||||
| public IEnumerable<ModuleInfo> Modules => _moduleDefs.Select(x => x); | public IEnumerable<ModuleInfo> Modules => _moduleDefs.Select(x => x); | ||||
| public IEnumerable<CommandInfo> Commands => _moduleDefs.SelectMany(x => x.Commands); | public IEnumerable<CommandInfo> Commands => _moduleDefs.SelectMany(x => x.Commands); | ||||
| @@ -36,7 +41,11 @@ namespace Discord.Commands | |||||
| _separatorChar = config.SeparatorChar; | _separatorChar = config.SeparatorChar; | ||||
| _defaultRunMode = config.DefaultRunMode; | _defaultRunMode = config.DefaultRunMode; | ||||
| if (_defaultRunMode == RunMode.Default) | if (_defaultRunMode == RunMode.Default) | ||||
| throw new InvalidOperationException("The default run mode cannot be set to Default, it must be one of Sync, Mixed, or Async"); | |||||
| 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); | |||||
| _cmdLogger = _logManager.CreateLogger("Command"); | |||||
| _moduleLock = new SemaphoreSlim(1, 1); | _moduleLock = new SemaphoreSlim(1, 1); | ||||
| _typedModuleDefs = new ConcurrentDictionary<Type, ModuleInfo>(); | _typedModuleDefs = new ConcurrentDictionary<Type, ModuleInfo>(); | ||||
| @@ -8,5 +8,8 @@ | |||||
| public char SeparatorChar { get; set; } = ' '; | public char SeparatorChar { get; set; } = ' '; | ||||
| /// <summary> Should commands be case-sensitive? </summary> | /// <summary> Should commands be case-sensitive? </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 Log event. </summary> | |||||
| public LogSeverity LogLevel { get; set; } = LogSeverity.Info; | |||||
| } | } | ||||
| } | } | ||||
| @@ -137,16 +137,48 @@ namespace Discord.Commands | |||||
| return ExecuteResult.FromError(result); | return ExecuteResult.FromError(result); | ||||
| } | } | ||||
| await Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false); | |||||
| switch (RunMode) | switch (RunMode) | ||||
| { | { | ||||
| case RunMode.Sync: //Always sync | case RunMode.Sync: //Always sync | ||||
| await _action(context, args, map).ConfigureAwait(false); | |||||
| try | |||||
| { | |||||
| await _action(context, args, map).ConfigureAwait(false); | |||||
| } | |||||
| catch (Exception ex) | |||||
| { | |||||
| ex = new CommandException(this, context, ex); | |||||
| await Module.Service._cmdLogger.ErrorAsync(ex).ConfigureAwait(false); | |||||
| throw; | |||||
| } | |||||
| await Module.Service._cmdLogger.VerboseAsync($"Executed {GetLogText(context)}").ConfigureAwait(false); | |||||
| break; | break; | ||||
| case RunMode.Mixed: //Sync until first await statement | case RunMode.Mixed: //Sync until first await statement | ||||
| var t1 = _action(context, args, map); | |||||
| var t1 = _action(context, args, map).ContinueWith(async t => | |||||
| { | |||||
| if (t.IsFaulted) | |||||
| { | |||||
| var ex = new CommandException(this, context, t.Exception); | |||||
| await Module.Service._cmdLogger.ErrorAsync(ex).ConfigureAwait(false); | |||||
| } | |||||
| else | |||||
| await Module.Service._cmdLogger.VerboseAsync($"Executed {GetLogText(context)}").ConfigureAwait(false); | |||||
| }); | |||||
| break; | break; | ||||
| case RunMode.Async: //Always async | case RunMode.Async: //Always async | ||||
| var t2 = Task.Run(() => _action(context, args, map)); | |||||
| var t2 = Task.Run(() => | |||||
| { | |||||
| var _ = _action(context, args, map).ContinueWith(async t => | |||||
| { | |||||
| if (t.IsFaulted) | |||||
| { | |||||
| var ex = new CommandException(this, context, t.Exception); | |||||
| await Module.Service._cmdLogger.ErrorAsync(ex).ConfigureAwait(false); | |||||
| } | |||||
| else | |||||
| await Module.Service._cmdLogger.VerboseAsync($"Executed {GetLogText(context)}").ConfigureAwait(false); | |||||
| }); | |||||
| }); | |||||
| break; | break; | ||||
| } | } | ||||
| return ExecuteResult.FromSuccess(); | return ExecuteResult.FromSuccess(); | ||||
| @@ -189,5 +221,13 @@ namespace Discord.Commands | |||||
| private static T[] ConvertParamsList<T>(IEnumerable<object> paramsList) | private static T[] ConvertParamsList<T>(IEnumerable<object> paramsList) | ||||
| => paramsList.Cast<T>().ToArray(); | => paramsList.Cast<T>().ToArray(); | ||||
| internal string GetLogText(ICommandContext context) | |||||
| { | |||||
| if (context.Guild != null) | |||||
| return $"\"{Name}\" for {context.User} in {context.Guild}/{context.Channel}"; | |||||
| else | |||||
| return $"\"{Name}\" for {context.User} in {context.Channel}"; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,11 +1,9 @@ | |||||
| using Discord.Commands.Builders; | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Discord.Commands.Builders; | |||||
| using System.Reflection; | |||||
| namespace Discord.Commands | namespace Discord.Commands | ||||
| { | { | ||||
| public class ParameterInfo | public class ParameterInfo | ||||
| @@ -22,7 +22,7 @@ namespace Discord | |||||
| /// <summary> Gets or sets how a request should act in the case of an error, by default. </summary> | /// <summary> Gets or sets how a request should act in the case of an error, by default. </summary> | ||||
| 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 LogMessage event. </summary> | |||||
| /// <summary> Gets or sets the minimum log level severity that will be sent to the Log event. </summary> | |||||
| public LogSeverity LogLevel { get; set; } = LogSeverity.Info; | public LogSeverity LogLevel { get; set; } = LogSeverity.Info; | ||||
| /// <summary> Gets or sets whether the initial log entry should be printed. </summary> | /// <summary> Gets or sets whether the initial log entry should be printed. </summary> | ||||