Skip to content

Commit

Permalink
Improve python exception parsing (QuantConnect#5831)
Browse files Browse the repository at this point in the history
* Improve python exception parsing

- Improve python exception parsing adding support for line shift. Adding
  unit tests

* PythonException revert change

* Centralized and normalize algorithm runtime handling

* Adding support for C# line and file exception report
  • Loading branch information
Martin-Molinero authored Aug 6, 2021
1 parent 646adb9 commit b77f012
Show file tree
Hide file tree
Showing 23 changed files with 320 additions and 169 deletions.
9 changes: 4 additions & 5 deletions Common/Brokerages/DefaultBrokerageMessageHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ public void Handle(BrokerageMessageEvent message)
break;

case BrokerageMessageType.Error:
_algorithm.Error($"Brokerage Error: {message.Message}");
_algorithm.RunTimeError = new Exception(message.Message);
// unexpected error, we need to close down shop
_algorithm.SetRuntimeError(new Exception(message.Message), "Brokerage Error");
break;

case BrokerageMessageType.Disconnect:
Expand Down Expand Up @@ -181,9 +181,8 @@ private void CheckReconnected(BrokerageMessageEvent message)
if (!_connected)
{
Log.Error("DefaultBrokerageMessageHandler.Handle(): Still disconnected, goodbye.");
_algorithm.Error($"Brokerage Disconnect: {message.Message}");
_algorithm.RunTimeError = new Exception(message.Message);
_algorithm.SetRuntimeError(new Exception(message.Message), "Brokerage Disconnect");
}
}
}
}
}
3 changes: 1 addition & 2 deletions Common/Exceptions/DllNotFoundPythonExceptionInterpreter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
*/

using System;
using QuantConnect.Util;

namespace QuantConnect.Exceptions
{
Expand Down Expand Up @@ -64,4 +63,4 @@ public Exception Interpret(Exception exception, IExceptionInterpreter innerInter
return new DllNotFoundException(message, dnfe);
}
}
}
}
14 changes: 6 additions & 8 deletions Common/Exceptions/InvalidTokenPythonExceptionInterpreter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,27 @@

using System;
using Python.Runtime;
using QuantConnect.Util;

namespace QuantConnect.Exceptions
{
/// <summary>
/// Interprets <see cref="InvalidTokenPythonExceptionInterpreter"/> instances
/// </summary>
public class InvalidTokenPythonExceptionInterpreter : IExceptionInterpreter
public class InvalidTokenPythonExceptionInterpreter : PythonExceptionInterpreter
{
/// <summary>
/// Determines the order that an instance of this class should be called
/// </summary>
public int Order => 0;
public override int Order => 0;

/// <summary>
/// Determines if this interpreter should be applied to the specified exception.
/// </summary>
/// <param name="exception">The exception to check</param>
/// <returns>True if the exception can be interpreted, false otherwise</returns>
public bool CanInterpret(Exception exception)
public override bool CanInterpret(Exception exception)
{
return
exception?.GetType() == typeof(PythonException) &&
return base.CanInterpret(exception) &&
exception.Message.Contains("SyntaxError") &&
exception.Message.Contains("invalid token");
}
Expand All @@ -48,7 +46,7 @@ public bool CanInterpret(Exception exception)
/// <param name="exception">The exception to be interpreted</param>
/// <param name="innerInterpreter">An interpreter that should be applied to the inner exception.</param>
/// <returns>The interpreted exception</returns>
public Exception Interpret(Exception exception, IExceptionInterpreter innerInterpreter)
public override Exception Interpret(Exception exception, IExceptionInterpreter innerInterpreter)
{
var pe = (PythonException)exception;

Expand All @@ -58,4 +56,4 @@ public Exception Interpret(Exception exception, IExceptionInterpreter innerInter
return new Exception($"{message}{Environment.NewLine} in {errorLine}{Environment.NewLine}", pe);
}
}
}
}
15 changes: 7 additions & 8 deletions Common/Exceptions/KeyErrorPythonExceptionInterpreter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,30 @@
*/

using System;
using System.Collections.Generic;
using Python.Runtime;
using QuantConnect.Util;
using System.Collections.Generic;

namespace QuantConnect.Exceptions
{
/// <summary>
/// Interprets <see cref="KeyErrorPythonExceptionInterpreter"/> instances
/// </summary>
public class KeyErrorPythonExceptionInterpreter : IExceptionInterpreter
public class KeyErrorPythonExceptionInterpreter : PythonExceptionInterpreter
{
/// <summary>
/// Determines the order that an instance of this class should be called
/// </summary>
public int Order => 0;
public override int Order => 0;

/// <summary>
/// Determines if this interpreter should be applied to the specified exception.
/// </summary>
/// <param name="exception">The exception to check</param>
/// <returns>True if the exception can be interpreted, false otherwise</returns>
public bool CanInterpret(Exception exception)
public override bool CanInterpret(Exception exception)
{
return
exception?.GetType() == typeof(PythonException) &&
return base.CanInterpret(exception) &&
exception.Message.Contains("KeyError");
}
/// <summary>
Expand All @@ -47,7 +46,7 @@ public bool CanInterpret(Exception exception)
/// <param name="exception">The exception to be interpreted</param>
/// <param name="innerInterpreter">An interpreter that should be applied to the inner exception.</param>
/// <returns>The interpreted exception</returns>
public Exception Interpret(Exception exception, IExceptionInterpreter innerInterpreter)
public override Exception Interpret(Exception exception, IExceptionInterpreter innerInterpreter)
{
var pe = (PythonException)exception;

Expand All @@ -67,4 +66,4 @@ public Exception Interpret(Exception exception, IExceptionInterpreter innerInter
return new KeyNotFoundException(message, pe);
}
}
}
}
13 changes: 6 additions & 7 deletions Common/Exceptions/NoMethodMatchPythonExceptionInterpreter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,21 @@ namespace QuantConnect.Exceptions
/// <summary>
/// Interprets <see cref="NoMethodMatchPythonExceptionInterpreter"/> instances
/// </summary>
public class NoMethodMatchPythonExceptionInterpreter : IExceptionInterpreter
public class NoMethodMatchPythonExceptionInterpreter : PythonExceptionInterpreter
{
/// <summary>
/// Determines the order that an instance of this class should be called
/// </summary>
public int Order => 0;
public override int Order => 0;

/// <summary>
/// Determines if this interpreter should be applied to the specified exception.
/// </summary>
/// <param name="exception">The exception to check</param>
/// <returns>True if the exception can be interpreted, false otherwise</returns>
public bool CanInterpret(Exception exception)
public override bool CanInterpret(Exception exception)
{
return
exception?.GetType() == typeof(PythonException) &&
return base.CanInterpret(exception) &&
exception.Message.Contains("TypeError") &&
exception.Message.Contains("No method match");
}
Expand All @@ -48,7 +47,7 @@ public bool CanInterpret(Exception exception)
/// <param name="exception">The exception to be interpreted</param>
/// <param name="innerInterpreter">An interpreter that should be applied to the inner exception.</param>
/// <returns>The interpreted exception</returns>
public Exception Interpret(Exception exception, IExceptionInterpreter innerInterpreter)
public override Exception Interpret(Exception exception, IExceptionInterpreter innerInterpreter)
{
var pe = (PythonException)exception;

Expand All @@ -61,4 +60,4 @@ public Exception Interpret(Exception exception, IExceptionInterpreter innerInter
return new MissingMethodException(message, pe);
}
}
}
}
11 changes: 5 additions & 6 deletions Common/Exceptions/PythonExceptionInterpreter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,26 @@ public class PythonExceptionInterpreter : IExceptionInterpreter
/// <summary>
/// Determines the order that an instance of this class should be called
/// </summary>
public int Order => int.MaxValue;
public virtual int Order => int.MaxValue - 1;

/// <summary>
/// Determines if this interpreter should be applied to the specified exception. f
/// </summary>
/// <param name="exception">The exception to check</param>
/// <returns>True if the exception can be interpreted, false otherwise</returns>
public bool CanInterpret(Exception exception) => exception?.GetType() == typeof(PythonException);
public virtual bool CanInterpret(Exception exception) => exception?.GetType() == typeof(PythonException);

/// <summary>
/// Interprets the specified exception into a new exception
/// </summary>
/// <param name="exception">The exception to be interpreted</param>
/// <param name="innerInterpreter">An interpreter that should be applied to the inner exception.</param>
/// <returns>The interpreted exception</returns>
public Exception Interpret(Exception exception, IExceptionInterpreter innerInterpreter)
public virtual Exception Interpret(Exception exception, IExceptionInterpreter innerInterpreter)
{
var pe = (PythonException)exception;

var message = pe.Message + PythonUtil.PythonExceptionStackParser(pe.StackTrace);
return new Exception(message, pe);
return new Exception(PythonUtil.PythonExceptionParser(pe), pe);
}
}
}
}
10 changes: 8 additions & 2 deletions Common/Exceptions/StackExceptionInterpreter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using QuantConnect.Logging;
using System.Collections.Generic;

namespace QuantConnect.Exceptions
{
Expand All @@ -28,6 +28,12 @@ public class StackExceptionInterpreter : IExceptionInterpreter
{
private readonly List<IExceptionInterpreter> _interpreters;

/// <summary>
/// Stack interpreter instance
/// </summary>
public static readonly Lazy<StackExceptionInterpreter> Instance = new Lazy<StackExceptionInterpreter>(
() => StackExceptionInterpreter.CreateFromAssemblies(AppDomain.CurrentDomain.GetAssemblies()));

/// <summary>
/// Determines the order that an instance of this class should be called
/// </summary>
Expand Down Expand Up @@ -66,7 +72,7 @@ public bool CanInterpret(Exception exception)
/// configured in the <see cref="StackExceptionInterpreter"/>. Individual implementations *may* ignore
/// this value if required.</param>
/// <returns>The interpreted exception</returns>
public Exception Interpret(Exception exception, IExceptionInterpreter innerInterpreter)
public Exception Interpret(Exception exception, IExceptionInterpreter innerInterpreter = null)
{
if (exception == null)
{
Expand Down
76 changes: 76 additions & 0 deletions Common/Exceptions/SystemExceptionInterpreter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using System;
using System.Text.RegularExpressions;

namespace QuantConnect.Exceptions
{
/// <summary>
/// Base handler that will try get an exception file and line
/// </summary>
public class SystemExceptionInterpreter : IExceptionInterpreter
{
private static Regex FileAndLineRegex = new Regex("(\\w+.cs:line \\d+)", RegexOptions.Compiled);

/// <summary>
/// Determines the order that an instance of this class should be called
/// </summary>
public int Order => int.MaxValue;

/// <summary>
/// Determines if this interpreter should be applied to the specified exception. f
/// </summary>
/// <param name="exception">The exception to check</param>
/// <returns>True if the exception can be interpreted, false otherwise</returns>
public bool CanInterpret(Exception exception) => true;

/// <summary>
/// Interprets the specified exception into a new exception
/// </summary>
/// <param name="exception">The exception to be interpreted</param>
/// <param name="innerInterpreter">An interpreter that should be applied to the inner exception.</param>
/// <returns>The interpreted exception</returns>
public Exception Interpret(Exception exception, IExceptionInterpreter innerInterpreter)
{
if (!TryGetLineAndFile(exception.StackTrace, out var fileAndLine))
{
return exception;
}
return new Exception(exception.Message + fileAndLine, exception);
}

/// <summary>
/// Helper method to get the file and line from a C# stacktrace
/// </summary>
public static bool TryGetLineAndFile(string stackTrace, out string fileAndLine)
{
fileAndLine = null;
if (stackTrace != null)
{
var match = FileAndLineRegex.Match(stackTrace);
if (match.Success)
{
foreach (Match lineCapture in match.Captures)
{
fileAndLine = $" in {lineCapture.Groups[1].Value}" ;
return true;
}
}
}
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,21 @@ namespace QuantConnect.Exceptions
/// <summary>
/// Interprets <see cref="UnsupportedOperandPythonExceptionInterpreter"/> instances
/// </summary>
public class UnsupportedOperandPythonExceptionInterpreter : IExceptionInterpreter
public class UnsupportedOperandPythonExceptionInterpreter : PythonExceptionInterpreter
{
/// <summary>
/// Determines the order that an instance of this class should be called
/// </summary>
public int Order => 0;
public override int Order => 0;

/// <summary>
/// Determines if this interpreter should be applied to the specified exception.
/// </summary>
/// <param name="exception">The exception to check</param>
/// <returns>True if the exception can be interpreted, false otherwise</returns>
public bool CanInterpret(Exception exception)
public override bool CanInterpret(Exception exception)
{
return
exception?.GetType() == typeof(PythonException) &&
return base.CanInterpret(exception) &&
exception.Message.Contains("TypeError") &&
exception.Message.Contains("unsupported operand type");
}
Expand All @@ -48,7 +47,7 @@ public bool CanInterpret(Exception exception)
/// <param name="exception">The exception to be interpreted</param>
/// <param name="innerInterpreter">An interpreter that should be applied to the inner exception.</param>
/// <returns>The interpreted exception</returns>
public Exception Interpret(Exception exception, IExceptionInterpreter innerInterpreter)
public override Exception Interpret(Exception exception, IExceptionInterpreter innerInterpreter)
{
var pe = (PythonException)exception;

Expand All @@ -59,4 +58,4 @@ public Exception Interpret(Exception exception, IExceptionInterpreter innerInter
return new Exception(message, pe);
}
}
}
}
12 changes: 12 additions & 0 deletions Common/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
using NodaTime.TimeZones;
using QuantConnect.Configuration;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Exceptions;
using QuantConnect.Securities.FutureOption;
using QuantConnect.Securities.Option;

Expand Down Expand Up @@ -2982,6 +2983,17 @@ public static OrderDirection GetOrderDirection(decimal quantity)
}
}

/// <summary>
/// Helper method to set an algorithm runtime exception in a normalized fashion
/// </summary>
public static void SetRuntimeError(this IAlgorithm algorithm, Exception exception, string context)
{
Log.Error(exception, $"Extensions.SetRuntimeError(): RuntimeError at {algorithm.UtcTime} UTC. Context: {context}");
exception = StackExceptionInterpreter.Instance.Value.Interpret(exception);
algorithm.RunTimeError = exception;
algorithm.SetStatus(AlgorithmStatus.RuntimeError);
}

/// <summary>
/// Creates a <see cref="OptionChainUniverse"/> for a given symbol
/// </summary>
Expand Down
Loading

0 comments on commit b77f012

Please sign in to comment.