@@ -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> | ||||