Related
I have this kind of code, with purpose is to wrap UnityEngine.Debug.Log so I can disable them all on production also so that I can look/filter up later.
using System;
public enum LogType
{
DEBUG,
CRITICAL
}
public class LogHelper
{
public static void Log(LogType lt, string format, params object[] objs)
{
if (lt == LogType.CRITICAL)
{
// StackTrace st = new StackTrace(new StackFrame(true));
// Console.WriteLine(" Stack trace for current level: {0}", st.ToString());
// StackFrame sf = st.GetFrame(0);
// Console.WriteLine(" File: {0}", sf.GetFileName());
// Console.WriteLine(" Method: {0}", sf.GetMethod().Name);
// Console.WriteLine(" Line Number: {0}", sf.GetFileLineNumber());
// Console.WriteLine(" Column Number: {0}", sf.GetFileColumnNumber());
}
// TODO: write to /tmp file too
UnityEngine.Debug.Log("[" + lt + "] " + String.Format(format, objs));
}
public static void Critical(string format, params object[] objs)
{
Log(LogType.CRITICAL,format, objs);
}
public static void Debug(string format, params object[] objs)
{
Log(LogType.DEBUG,format, objs);
}
}
The problem is, when i call those LogHelper.Debug("something"), the Unity Editor's Log when double clicked will go to that code (one that calls UnityEngine.Debug.Log) instead of the source that call that LogHelper.Debug. How to make it show the caller instead of the LogHelper when I doubleclick the log?
I am not sure, but try:
public static class MyDebug{
public static delegate void TestDelegate(object message);
#if (NOEDITOR)
public static TestDelegate Log =(x)=>{};
#else
public static TestDelegate Log = Debug.Log;
#endif
}
then, control this defining NOEDITOR
One possible solution is to use Editor Console Pro from the Unity Asset Store.
Here are some of the listed features that are relevant to your question (emphasis mine):
See the source code surrounding each method call in the stack, allowing you to see and jump to the code around the log.
Open your code editor to any method or line in a log's stack by clicking on it, rather than just to the Debug.Log call.
[...]
Ignore custom Debug.Log classes in the stack, so double clicking never takes you to the wrong code.
However, it's not a free package (though the price is reasonable and it has excellent reviews).
You could also write your own Unity editor extension to implement something similar to Editor Console Pro using UnityEngine.Application.logMessageReceivedThreaded.
//U can use "LogPlayerBuildError" for this
/// ================================
/// Checks if the object of type T is null or not,
/// If object is null, prints a log message if 'enableLog' set to 'true'
/// https://answers.unity.com/questions/238229/debugconsole-console-clicking.html
/// ================================
public static bool IsNull<T>( this T classType, bool enableLog = true, MonoBehaviour monoInstance = null) where T : class
{
if(classType == null)
{ if(enableLog)
{ //if(classType.GetType().IsSubclassOf(typeof(MonoBehaviour)))
var frame = new System.Diagnostics.StackTrace(true).GetFrame(1);
string fileName = FormatFileName(frame.GetFileName());
int lineNum = frame.GetFileLineNumber();
int colomn = frame.GetFileColumnNumber();
string msg = "WARNING:- The instance of type " + typeof(T) + " is null!"
+ "\nFILE: " + fileName + " LINE: " + lineNum;
//Debug.LogWarning(msg, (UnityEngine.Object)monoInstance.gameObject);
var mUnityLog = typeof(UnityEngine.Debug).GetMethod("LogPlayerBuildError", BindingFlags.NonPublic | BindingFlags.Static);
mUnityLog.Invoke(null, new object[] { msg, fileName, lineNum, colomn });
}
return true;
}
return false;
}
Create a .dll of your extended Log classes and remove the source .cs files from the project folder. This will cause Unity to take you to the original code line rather than the helper class when double clicking in the default Unity console.
As outlined by user on Unity Forums here: Hide Method in Stack Trace Log
For a simple "how to .dll" you can follow this tutorial: Unity and Dlls
Recently had to solve this as was having the same frustration with Unity taking me to helper class rather than the source line. Old Post but still high in search engines so hopefully will help someone out.
Apologies about the (possibly) basic question, but I am attempting to split a string, and making one part of the string public, so I can call it later in the form in a different class.
It is a simple Skype chat bot, and it reads the message sent to me for processing. However, I am attempting to make it so that if someone sent a command with two words - e.g. !command name - the !command command will be processed, and later in the form, I will use the second part of the split string to be able to process it. Here is what I am attempting -
The splitting and reading of the message -
public void skype_MessageStatus(ChatMessage msg, TChatMessageStatus status)
{
if (msg.Body.IndexOf(trigger) == 0 && TChatMessageStatus.cmsReceived == status)
{
string command = msg.Body.Remove(0, trigger.Length).ToLower();
var splitted = command.Split(' ');
string command1 = splitted[0];
string name = splitted[1];
msg.Chat.SendMessage(nick + ProcessCommand(command1));
}
}
There are several other commands in this chat bot, so there is a switch containing different outcomes - as for !command, I have -
case "command":
result = command();
break;
And finally -
private string command()
{
WebRequest.Create("API I have" + name);
new WebClient().DownloadString("API I have" + name);
}
I would like to be able to use 'name' here, from the split message. Thanks, and any help is appreciated.
First, define the pattern you want to use to parse your inbound messages. It seems like you have multiple commands that probably have different parameters, but all commands take the following form:
![Command String] [Command Parameters]
So you will want a class that represents that:
public class ChatCommand
{
private string _command;
public string Command { get { return _command; } }
private string _parameters;
public string Parameters { get { return _parameters; } }
public ChatCommand(string command, string parameters) {
_command = command;
_parameters = parameters;
}
}
From there, you will need to adjust the method you posted to look like this. Notice that we are now telling split to stop splitting when it has 2 strings to return (basically splitting on the first space only).
public void skype_MessageStatus(ChatMessage msg, TChatMessageStatus status)
{
if (msg.Body.IndexOf(trigger) == 0 && TChatMessageStatus.cmsReceived == status)
{
string command = msg.Body.Remove(0, trigger.Length).ToLower();
var splitted = command.Split(new [] { ' ' }, 2);
var command1 = new ChatCommand(splitted[0], splitted[1])
msg.Chat.SendMessage(nick + ProcessCommand(command1));
}
}
The reason we are leaving the parameters in one string instead of splitting them out is because this gives you the flexibility to format parameters for different commands differently. For example, maybe you want to have a command to send a picture and the second parameters is a URL, or it could be JSON data or binary file data.
Now your ProcessCommand function can look like this:
public void ProcessCommand(ChatCommand command) {
switch(command.Command) {
case "command":
//I am code specific to that command and I should know what is contained in the Parameters property of the command
Console.WriteLine("Name is " + command.Parameters);
break;
}
}
Then you're done! You can add more commands with any parameters you would like.
Here is an example of what I want to do:
MessageBox.Show("Error line number " + CurrentLineNumber);
In the code above the CurrentLineNumber, should be the line number in the source code of this piece of code.
How can I do that?
In .NET 4.5 / C# 5, you can get the compiler to do this work for you, by writing a utility method that uses the new caller attributes:
using System.Runtime.CompilerServices;
static void SomeMethodSomewhere()
{
ShowMessage("Boo");
}
...
static void ShowMessage(string message,
[CallerLineNumber] int lineNumber = 0,
[CallerMemberName] string caller = null)
{
MessageBox.Show(message + " at line " + lineNumber + " (" + caller + ")");
}
This will display, for example:
Boo at line 39 (SomeMethodSomewhere)
There's also [CallerFilePath] which tells you the path of the original code file.
Use the StackFrame.GetFileLineNumber method, for example:
private static void ReportError(string message)
{
StackFrame callStack = new StackFrame(1, true);
MessageBox.Show("Error: " + message + ", File: " + callStack.GetFileName()
+ ", Line: " + callStack.GetFileLineNumber());
}
See Scott Hanselman's Blog entry for more information.
[Edit: Added the following]
For those using .Net 4.5 or later, consider the CallerFilePath, CallerMethodName and CallerLineNumber attributes in the System.Runtime.CompilerServices namespace. For example:
public void TraceMessage(string message,
[CallerMemberName] string callingMethod = "",
[CallerFilePath] string callingFilePath = "",
[CallerLineNumber] int callingFileLineNumber = 0)
{
// Write out message
}
The arguments must be string for CallerMemberName and CallerFilePath and an int for CallerLineNumber and must have a default value. Specifying these attributes on method parameters instructs the compiler to insert the appropriate value in the calling code at compile time, meaning it works through obfuscation. See Caller Information for more information.
I prefer one liners so:
int lineNumber = (new System.Diagnostics.StackFrame(0, true)).GetFileLineNumber();
In .NET 4.5 you can get the line number by creating the function:
static int LineNumber([System.Runtime.CompilerServices.CallerLineNumber] int lineNumber = 0)
{
return lineNumber;
}
Then each time you call LineNumber() you will have the current line. This has the advantage over any solution using the StackTrace that it should work in both debug and release.
So taking the original request of what is required, it would become:
MessageBox.Show("Error enter code here line number " + LineNumber());
This is building on the excellent answer by Marc Gravell.
For those who need a .NET 4.0+ method solution:
using System;
using System.IO;
using System.Diagnostics;
public static void Log(string message) {
StackFrame stackFrame = new System.Diagnostics.StackTrace(1).GetFrame(1);
string fileName = stackFrame.GetFileName();
string methodName = stackFrame.GetMethod().ToString();
int lineNumber = stackFrame.GetFileLineNumber();
Console.WriteLine("{0}({1}:{2})\n{3}", methodName, Path.GetFileName(fileName), lineNumber, message);
}
How to call:
void Test() {
Log("Look here!");
}
Output:
Void Test()(FILENAME.cs:104)
Look here!
Change the Console.WriteLine format how you like!
If its in a try catch block use this.
try
{
//Do something
}
catch (Exception ex)
{
System.Diagnostics.StackTrace trace = new System.Diagnostics.StackTrace(ex, true);
Console.WriteLine("Line: " + trace.GetFrame(0).GetFileLineNumber());
}
You only asked about the line number and with nullable project type, you then need to use a something like this
internal class Utils
{
public static int Line([CallerLineNumber] int? lineNumber =null)=>lineNumber;
}
in your code, if you like to get a line number you then just call
var line=Utils.Line();
if you are logging and you would like to document the line number in say logging than call the method like this
public void MyMethod(int someValue)
{
switch(someValue)
{
case 1:
if(abc<xyz)
{
logger.LogInformation("case value {someValue} this line {line} was true", someValue ,Utils.Line()-2);
}
break;
case 2:
logger.LogInformation("case value {someValue} this {line} was executed",someValue,Utils.Line());
break;
caste 3:
logger.LogInformation("case value {someValue} this {line} was executed",someValue,Utils.Line());
break;
}
}
You can extend this pattern with any of the other [CallerXXX] methods and not use them where ever, not just in the method parameters.
in the Nuget Package Walter I use a super cool class named ExceptionObject
if you import the NuGet package you have some nice extension methods on the Exception class as well as access to a CallStack showing the call chain including method parameters and parameter values of all methods called.
It's like a stack of an exception only with values showing how you got where you got with what values.
public void MyMethod()
{
try
{
//get me all methods, signatures, parameters line numbers file names etc used as well as assembly info of all assemblies used for documentation of how the code got here
var stack= new CallStack();
foreach( var frame in StackedFrames)
{
logger.LogDebug(frame.ToString());
}
}
catch(SqlException e)
{
var ex = new ExceptionObject(e);
logger.LogException(e,"{this} exception due to {message} {server} {procedure} TSQL-line:{sqlline}\n{TSQL}"
,e.GetType().Name
,e.Message
,ex.SqlServer
,ex.SqlProcedureName
,ex.SqlLineNumber
,ex.Tsql
,ex.CallStack);
}
catch(Exception e)
{
var ex = new ExceptionObject(e);
logger.LogException(e,"{this} exception due to {message} signature: signature}\nCallStack:", e.GetType().Name,e.Message,ex.Signature,ex.CallStack);
}
}
When an exception is thrown (while debugging in the IDE), i have the opportunity to view details of the exception:
But in code if i call exception.ToString() i do not get to see those useful details:
System.Data.SqlClient.SqlException (0x80131904): Could not find stored procedure 'FetchActiveUsers'.
[...snip stack trace...]
But Visual Studio has some magic where it can copy the exception to the clipboard:
Which gives the useful details:
System.Data.SqlClient.SqlException was unhandled by user code
Message=Could not find stored procedure 'FetchActiveUsers'.
Source=.Net SqlClient Data Provider
ErrorCode=-2146232060
Class=16
LineNumber=1
Number=2812
Procedure=""
Server=vader
State=62
StackTrace:
[...snip stack trace...]
InnerException:
Well i want that!
What would be the contents of:
String ExceptionToString(Exception ex)
{
//todo: Write useful routine
return ex.ToString();
}
that can accomplish the same magic. Is there a .NET function built in somewhere? Does Exception have a secret method somewhere to convert it to a string?
ErrorCode is specific to ExternalException, not Exception and LineNumber and Number are specific to SqlException, not Exception. Therefore, the only way to get these properties from a general extension method on Exception is to use reflection to iterate over all of the public properties.
So you'll have to say something like:
public static string GetExceptionDetails(this Exception exception) {
var properties = exception.GetType()
.GetProperties();
var fields = properties
.Select(property => new {
Name = property.Name,
Value = property.GetValue(exception, null)
})
.Select(x => String.Format(
"{0} = {1}",
x.Name,
x.Value != null ? x.Value.ToString() : String.Empty
));
return String.Join("\n", fields);
}
(Not tested for compliation issues.)
.NET 2.0 compatible answer:
public static string GetExceptionDetails(this Exception exception)
{
PropertyInfo[] properties = exception.GetType()
.GetProperties();
List<string> fields = new List<string>();
foreach(PropertyInfo property in properties) {
object value = property.GetValue(exception, null);
fields.Add(String.Format(
"{0} = {1}",
property.Name,
value != null ? value.ToString() : String.Empty
));
}
return String.Join("\n", fields.ToArray());
}
I first tried Jason's answer (at the top), which worked pretty well, but I also wanted:
To loop iteratively through inner exceptions and indent them.
Ignore null properties and increases readability of the output.
It includes the metadata in the Data property. (if any) but excludes the Data property itself. (its useless).
I now use this:
public static void WriteExceptionDetails(Exception exception, StringBuilder builderToFill, int level)
{
var indent = new string(' ', level);
if (level > 0)
{
builderToFill.AppendLine(indent + "=== INNER EXCEPTION ===");
}
Action<string> append = (prop) =>
{
var propInfo = exception.GetType().GetProperty(prop);
var val = propInfo.GetValue(exception);
if (val != null)
{
builderToFill.AppendFormat("{0}{1}: {2}{3}", indent, prop, val.ToString(), Environment.NewLine);
}
};
append("Message");
append("HResult");
append("HelpLink");
append("Source");
append("StackTrace");
append("TargetSite");
foreach (DictionaryEntry de in exception.Data)
{
builderToFill.AppendFormat("{0} {1} = {2}{3}", indent, de.Key, de.Value, Environment.NewLine);
}
if (exception.InnerException != null)
{
WriteExceptionDetails(exception.InnerException, builderToFill, ++level);
}
}
Call like this:
var builder = new StringBuilder();
WriteExceptionDetails(exception, builder, 0);
return builder.ToString();
This comprehensive answer handles writing out:
The Data collection property found on all exceptions (The accepted answer does not do this).
Any other custom properties added to the exception.
Recursively writes out the InnerException (The accepted answer does not do this).
Writes out the collection of exceptions contained within the AggregateException.
It also writes out the properties of the exceptions in a nicer order. It's using C# 6.0 but should be very easy for you to convert to older versions if necessary.
public static class ExceptionExtensions
{
public static string ToDetailedString(this Exception exception)
{
if (exception == null)
{
throw new ArgumentNullException(nameof(exception));
}
return ToDetailedString(exception, ExceptionOptions.Default);
}
public static string ToDetailedString(this Exception exception, ExceptionOptions options)
{
var stringBuilder = new StringBuilder();
AppendValue(stringBuilder, "Type", exception.GetType().FullName, options);
foreach (PropertyInfo property in exception
.GetType()
.GetProperties()
.OrderByDescending(x => string.Equals(x.Name, nameof(exception.Message), StringComparison.Ordinal))
.ThenByDescending(x => string.Equals(x.Name, nameof(exception.Source), StringComparison.Ordinal))
.ThenBy(x => string.Equals(x.Name, nameof(exception.InnerException), StringComparison.Ordinal))
.ThenBy(x => string.Equals(x.Name, nameof(AggregateException.InnerExceptions), StringComparison.Ordinal)))
{
var value = property.GetValue(exception, null);
if (value == null && options.OmitNullProperties)
{
if (options.OmitNullProperties)
{
continue;
}
else
{
value = string.Empty;
}
}
AppendValue(stringBuilder, property.Name, value, options);
}
return stringBuilder.ToString().TrimEnd('\r', '\n');
}
private static void AppendCollection(
StringBuilder stringBuilder,
string propertyName,
IEnumerable collection,
ExceptionOptions options)
{
stringBuilder.AppendLine($"{options.Indent}{propertyName} =");
var innerOptions = new ExceptionOptions(options, options.CurrentIndentLevel + 1);
var i = 0;
foreach (var item in collection)
{
var innerPropertyName = $"[{i}]";
if (item is Exception)
{
var innerException = (Exception)item;
AppendException(
stringBuilder,
innerPropertyName,
innerException,
innerOptions);
}
else
{
AppendValue(
stringBuilder,
innerPropertyName,
item,
innerOptions);
}
++i;
}
}
private static void AppendException(
StringBuilder stringBuilder,
string propertyName,
Exception exception,
ExceptionOptions options)
{
var innerExceptionString = ToDetailedString(
exception,
new ExceptionOptions(options, options.CurrentIndentLevel + 1));
stringBuilder.AppendLine($"{options.Indent}{propertyName} =");
stringBuilder.AppendLine(innerExceptionString);
}
private static string IndentString(string value, ExceptionOptions options)
{
return value.Replace(Environment.NewLine, Environment.NewLine + options.Indent);
}
private static void AppendValue(
StringBuilder stringBuilder,
string propertyName,
object value,
ExceptionOptions options)
{
if (value is DictionaryEntry)
{
DictionaryEntry dictionaryEntry = (DictionaryEntry)value;
stringBuilder.AppendLine($"{options.Indent}{propertyName} = {dictionaryEntry.Key} : {dictionaryEntry.Value}");
}
else if (value is Exception)
{
var innerException = (Exception)value;
AppendException(
stringBuilder,
propertyName,
innerException,
options);
}
else if (value is IEnumerable && !(value is string))
{
var collection = (IEnumerable)value;
if (collection.GetEnumerator().MoveNext())
{
AppendCollection(
stringBuilder,
propertyName,
collection,
options);
}
}
else
{
stringBuilder.AppendLine($"{options.Indent}{propertyName} = {value}");
}
}
}
public struct ExceptionOptions
{
public static readonly ExceptionOptions Default = new ExceptionOptions()
{
CurrentIndentLevel = 0,
IndentSpaces = 4,
OmitNullProperties = true
};
internal ExceptionOptions(ExceptionOptions options, int currentIndent)
{
this.CurrentIndentLevel = currentIndent;
this.IndentSpaces = options.IndentSpaces;
this.OmitNullProperties = options.OmitNullProperties;
}
internal string Indent { get { return new string(' ', this.IndentSpaces * this.CurrentIndentLevel); } }
internal int CurrentIndentLevel { get; set; }
public int IndentSpaces { get; set; }
public bool OmitNullProperties { get; set; }
}
Top Tip - Logging Exceptions
Most people will be using this code for logging. Consider using Serilog with my Serilog.Exceptions NuGet package which also logs all properties of an exception but does it faster and without reflection in the majority of cases. Serilog is a very advanced logging framework which is all the rage at the time of writing.
Top Tip - Human Readable Stack Traces
You can use the Ben.Demystifier NuGet package to get human readable stack traces for your exceptions or the serilog-enrichers-demystify NuGet package if you are using Serilog. If you are using .NET Core 2.1, then this feature comes built in.
For people who don't want to mess with overriding, this simple non-intrusive method might be enough:
public static string GetExceptionDetails(Exception exception)
{
return "Exception: " + exception.GetType()
+ "\r\nInnerException: " + exception.InnerException
+ "\r\nMessage: " + exception.Message
+ "\r\nStackTrace: " + exception.StackTrace;
}
It does not show the SQLException-specific details you want, though...
There is no secret method. You could probably just override the ToString() method and build the string you want.
Things like ErrorCode and Message are just properties of the exception that you can add to the desired string output.
Update: After re-reading your question and thinking more about this, Jason's answer is more likely what you are wanting. Overriding the ToString() method would only be helpful for exceptions that you created, not already implemented ones. It doesn't make sense to sub class existing exceptions just to add this functionality.
For displaying some details to user you should use ex.Message. For displaying to developers you will probably need ex.Message and ex.StackTrace.
There is no 'secret' method, you could consider Message property to be best fit for user friendly message.
Also be careful that in some case you may have inner exception in exception you catch which would be also useful to log.
You will probably have to manually construct that string by concatenating the various fields you are interested in.
Each left-side name is property in the Exception. If you want to display Message field, you can do
return ex.Message;
Pretty simple. Likewise, the StackTrace can be displayed as below link.
A complete example of StackTrace: http://msdn.microsoft.com/en-us/library/system.exception.stacktrace.aspx
and Exception class: http://msdn.microsoft.com/en-us/library/system.exception.aspx
I think the exception serialization to JSON is nice option. Sample result:
{
"Errors": [{
"Source": ".Net SqlClient Data Provider",
"Number": -1,
"Class": 20,
"Server": "111.168.222.70",
"Message": "A transport-level error has occurred when receiving results from the server. (provider: Session Provider, error: 19 - Physical connection is not usable)"
}
],
"ClientConnectionId": "b1854037-51e4-4943-94b4-72b7bb4c6ab7",
"ClassName": "System.Data.SqlClient.SqlException",
"Message": "A transport-level error has occurred when receiving results from the server. (provider: Session Provider, error: 19 - Physical connection is not usable)",
"Data": {
"HelpLink.ProdName": "Microsoft SQL Server",
"HelpLink.EvtSrc": "MSSQLServer",
"HelpLink.EvtID": "-1",
"HelpLink.BaseHelpUrl": "http://go.microsoft.com/fwlink",
"HelpLink.LinkId": "20476"
},
"InnerException": null,
"HelpURL": null,
"StackTraceString": " at System.Data.SqlClient.SqlConnection.OnError ... DbExecutionStrategy.Execute[TResult](Func`1 operation)",
"RemoteStackTraceString": null,
"RemoteStackIndex": 0,
"ExceptionMethod": "8\nOnError\nSystem.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\nSystem.Data.SqlClient.SqlConnection\nVoid OnError(System.Data.SqlClient.SqlException, Boolean, System.Action`1[System.Action])",
"HResult": -2146232060,
"Source": ".Net SqlClient Data Provider",
"WatsonBuckets": null
}
If you call ToString on Exception object, you get the class name appended by the message, followed by inner exception and then the stack trace.
className + message + InnerException + stackTrace
Given that, InnerException and StackTrace are only added if they are not null. Also, the fields you have mentioned in the screenshot are not part of standard Exception class. Yes, exception does offer a public property called "Data", that contain additional user-defined information about the exception.
In visual studio that sort of information can be outputted by a debugger visualizer.
I assume that because it is possible to write your own debugger visualizer:
http://msdn.microsoft.com/en-us/library/e2zc529c.aspx
That in theory, if your can reverse engineer the built-in debugger visualizer for exceptions (if your can work out where they are stored) then you could use the same functionality.
EDIT:
Here is a post about where the debugger visualizers are kept: Where do I find Microsoft.VisualStudio.DebuggerVisualizers?
You might be able to use it for your own purposes.
I wrote a method Assert():
[System.Diagnostics.Conditional("DEBUG")]
internal static void Assert(bool condition)
{
if (!condition)
{
var message =
"Line:" + (new System.Diagnostics.StackFrame(1)).GetFileLineNumber() + "\r\n" +
"Column:" + (new System.Diagnostics.StackFrame(1)).GetFileColumnNumber() + "\r\n" +
"Where:" + (new System.Diagnostics.StackFrame(1)).GetMethod().Name;
Log("ASSERTION", message);
}
}
Why do I have both line and column being equal to 0, when triggered? It supposed to be the place where Debug.Assert(false) is called.
Regards,
You need to use the StackFrame(int, bool) overload and specify true as the second argument. It looks like just the StackFrame(int) overload doesn't capture source information.
Sample code:
using System.Diagnostics;
...
[Conditional("DEBUG")]
internal static void Assert(bool condition)
{
if (!condition)
{
StackFrame frame = new StackFrame(1, true);
var message = string.Format("Line: {0}\r\nColumn: {1}\r\nWhere:{2}",
frame.GetFileLineNumber(),
frame.GetFileColumnNumber(),
frame.GetMethod().Name);
Log("ASSERTION", message);
}
}
(Looking at your comments by the way, you will need the PDB files. That's where the debug information is stored. It's not at all clear to me whether this will work in a SQLCLR trigger, to be honest. The above works for me in a console app, but that's all I can say...)