javascript Mermaid C#

利用Mermaid產出例外錯誤流程圖

劉仁竹 2022/12/29 11:41:50
2253

 

前言

最近研究繪製流程圖時,無意間發現了「Mermaid」,Mermaid是由JavaScript為基礎建構的開源軟體,其最主的特色是利用簡易的文字語法來繪製各種不同類型的圖示:Flowchart 流程圖、Sequence Diagram 循序圖、Class Diagram 類別圖、State Diagram 狀態圖、Entity Relationship Diagram 實體關係圖、User Journey 用戶旅程圖、Gantt 甘特圖、Pie Chart 圓餅圖、Git Graph 版控圖

網站也提供線上操作展示範例

下面文章環境是選擇使用C# asp.net core MVC,例外錯誤訊息利用Flowchart(流程圖)產出視覺化的圖示。

(1)參考文件範例

(2)建置例外錯誤訊息範例

(3)分析錯誤訊息

(4)設定目標Flowchart範本

(5)開發錯誤訊息轉換方法

(6)前端設置

(7)輸出結果

(8)參考文獻

 

(1)參考文件範例

Flowchart 文字語法

flowchart TB
    c1-->a2
    subgraph one
    a1-->a2
    end
    subgraph two
    b1-->b2
    end
    subgraph three
    c1-->c2
    end

由文件說明及範例參考先觀察出語法如何撰寫

由文件說明及範例參考先觀察出語法如何撰寫

 

(2)建置例外錯誤訊息範例

 

這邊先簡單寫一個會沒有檢查時間物件null而導致出現例外錯誤的範例

public class HelpObj
{
	public static void TestTime()
	{
		DateTime? dt = null;
		string testError = dt.ToStrForTime();
	}
}

public static class DateTimeExtensions
{

	public static string ToStrForTime(this DateTime? dateTime)
	{
		return dateTime.Value.ToString("yyyy MM dd");
	}
}

其例外錯誤的StackTrace就會是

at System.Nullable`1.get_Value() at Common.Util.Extensions.DateTimeExtensions.ToStrForTime(Nullable`1 dateTime) in ...\Common\Util\Extensions\DateTimeExtensions.cs:line 38 at Common.Util.HelpObj.TestTime() in ..\CoreMVC\Common\Util\HelpObj.cs:line 21 at CoreMVC.Controllers.HomeController.Privacy() in ..\CoreMVC\CoreMVC\Controllers\HomeController.cs:line 34

 

(3)分析錯誤訊息

這些錯誤訊息中

System.Nullable`1.get_Value()

Common.Util.Extensions.DateTimeExtensions.ToStrForTime(Nullable`1 dateTime)

Common.Util.HelpObj.TestTime()

CoreMVC.Controllers.HomeController.Privacy()

這四段就是我們要轉換為Flowchart語法的關鍵

將錯誤訊息分兩個部分

1.程式執行流程

從訊息中可得到 Privacy() > TestTime() > ToStrForTime() > get_Value()

2.物件所在位置

注意第二三行的有共同專案及目錄名稱

 

(4)設定目標Flowchart範本

將錯誤訊息套到Flowchart語法中

Privacy(Privacy: 34):Privacy()是物件所在位置,()內為顯示文字,這邊顯示方法及程式行數

graph LR

	ToStrForTime-->get_Value
	TestTime-->ToStrForTime
	Privacy-->TestTime

	subgraph System
	subgraph Nullable`1
	get_Value
	end
	end

	subgraph Common
	
	subgraph Util
	subgraph Extensions
	subgraph DeTimeExtensions
	ToStrForTime(ToStrForTime: 38)
	end
	end
	end

	subgraph HelpObj
	TestTime(TestTime: 21)
	end
	
	end

	subgraph CoreMVC
	subgraph Controllers
	subgraph HomeController
	Privacy(Privacy: 34)
	end
	end
	end

 

(5)開發錯誤訊息轉換方法

有了目標,有可以開發model、及轉換方法

轉換暫存model

public class ErrorDiagram
{
	private List<ErrorDiagram> _child;

	public List<ErrorDiagram> Child 
	{
		get 
		{
			if (_child == null)
			{
				_child = new List<ErrorDiagram>();
			}
			return _child;
		}
	}

	/// <summary>
	/// Sub name
	/// </summary>
	public string SubGraph { get; set; }

	/// <summary>
	/// function name
	/// </summary>
	public string FunctionName { get; set; }

	/// <summary>
	/// error line
	/// </summary>
	public string LineNum { get; set; }

	/// <summary>
	/// 是否為function
	/// </summary>
	/// <returns></returns>
	public bool IsFunction()
	{
		return FunctionName != null;
	}

	/// <summary>
	/// view function & error linenum
	/// </summary>
	/// <returns></returns>
	public string FunctionView()
	{
		if (IsFunction() && string.IsNullOrEmpty(LineNum) != true)
		{
			return string.Format("{0}({0}:{1})", FunctionName, LineNum);
		}
		else if (IsFunction())
		{
			return FunctionName;
		}
		return string.Empty;
	}

	/// <summary>
	/// 是否有Sub
	/// </summary>
	/// <returns></returns>
	public bool HasSubChild()
	{
		if (Child != null)
		{
			return Child.Count > 0;
		}
		return false;
	}

	/// <summary>
	/// get mermaid flowchart code
	/// </summary>
	/// <returns></returns>
	public string GetDiagramCode()
	{
		// 
		if (IsFunction())
		{
			return FunctionView();
		}

		if (HasSubChild() && Child.Count == 1)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine($"subgraph {SubGraph}");
			stringBuilder.AppendLine(Child.FirstOrDefault().GetDiagramCode());
			stringBuilder.AppendLine("end");
			return stringBuilder.ToString();
		}
		else if (HasSubChild())
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine($"subgraph {SubGraph}");
			foreach (var item in Child)
			{
				stringBuilder.AppendLine(item.GetDiagramCode());
			}
			stringBuilder.AppendLine("end");
			return stringBuilder.ToString();
		}

		return "";
	}
}

 

轉換主要流程code

/// <summary>
/// 轉換為流程圖code
/// </summary>
/// <param name="StackTrace"></param>
/// <returns></returns>
public static string GetFlowChart(string stackTrace)
{
	StringReader strReader = new StringReader(stackTrace);
	string strline = strReader.ReadLine();
	List<ErrorDiagram> errorDiagrams = new List<ErrorDiagram>();
	List<string> functionName = new List<string>();
	while (strline != null)
	{
		var strfirstLast = strline.Split(" in ");
		string strfirst = strfirstLast.FirstOrDefault().Replace("at","").Trim();
		var strLastLineNum = strfirstLast.Length > 1 ? strfirstLast.LastOrDefault().Split(":line").LastOrDefault() : "";
		var subDiagrams = strfirst.Split(".").ToList();
		functionName.Add(subDiagrams.LastOrDefault().Split("(").FirstOrDefault());

		BuildErrorDiagrams(ref errorDiagrams, subDiagrams, strLastLineNum);

		strline = strReader.ReadLine();
	}
	StringBuilder stringBuilder = new StringBuilder();
	stringBuilder.AppendLine("graph LR");
	// flow
	for (int i = 0; i < functionName.Count-1; i++)
	{
		int parentIndex = i + 1;
		if (parentIndex < functionName.Count)
		{
			stringBuilder.AppendLine($"{functionName[parentIndex]}-->{functionName[i]}");
		}
	}
	// object site
	foreach (var item in errorDiagrams)
	{
		stringBuilder.AppendLine(item.GetDiagramCode());
	}
	
	string result = stringBuilder.ToString();
	return result;
}

 

建置各層訊息流程方法

private static void BuildErrorDiagrams(ref List<ErrorDiagram> errorDiagrams, List<string> subDiagrams,string lineNum) 
{
	//建置 Diagrams物件
	ErrorDiagram errorDiagramParent = null;
	foreach (var item in subDiagrams)
	{
		if (errorDiagramParent == null)
		{
			// first
			errorDiagramParent = errorDiagrams.Find(x => x.SubGraph == item);
			// check has
			if (errorDiagramParent == null)
			{
				errorDiagramParent = new ErrorDiagram()
				{
					SubGraph = item,
				};
				errorDiagrams.Add(errorDiagramParent);
			}
		}
		else if (item == subDiagrams.LastOrDefault())
		{
			// function
			errorDiagramParent.Child.Add(new ErrorDiagram()
			{
				FunctionName = item.Split("(").FirstOrDefault(),
				LineNum = lineNum,
			});
		}
		else
		{
			var errorDiagramChild = errorDiagramParent.Child.Find(x => x.SubGraph == item);
			// check has
			if (errorDiagramChild == null)
			{
				var errorDiagramNew = new ErrorDiagram()
				{
					SubGraph = item,
				};
				errorDiagramParent.Child.Add(errorDiagramNew);

				errorDiagramParent = errorDiagramNew;
			}
		}

	}
}

 

(6)前端設置

設置在執行Privacy頁面出錯時,將錯誤訊息導致DiagramError頁面

public class HomeController : Controller
{
	...
	
	public IActionResult Privacy()
	{
		try
		{
			//HelpObj.Test();
			HelpObj.TestTime();
		}
		catch (Exception ex)
		{
			TempData["error"] = JsonConvert.SerializeObject(new DiagramErrorModel
			{
				Message = ex.Message,
				StackTrace = ex.StackTrace
			});
			return RedirectToAction("DiagramError");
		}
		
		return View();
	}

	public IActionResult DiagramError()
	{
		DiagramErrorModel diagramErrorModel = new DiagramErrorModel();
		object temp = TempData["error"];
		if (temp != null)
		{
			diagramErrorModel = JsonConvert.DeserializeObject<DiagramErrorModel>(temp.ToString());
		}

		var diagramError = new DiagramErrorViewModel()
		{
			Message = diagramErrorModel.Message,
			DiagramCode = HelpMermaid.GetFlowChart(diagramErrorModel.StackTrace),
			StackTrace = diagramErrorModel.StackTrace
		};
		return View(diagramError);
	}
}

 

Error Model

public class DiagramErrorModel
{
	public string Message { get; set; }

	public string StackTrace { get; set; }
}

public class DiagramErrorViewModel
{
	public string Message { get; set; }

	public string DiagramCode { get; set; }

	public string StackTrace { get; set; }
}

 

參考文件加入script參考

 

顯示頁error view 加入script參考

@model DiagramErrorViewModel

<h1>錯誤流程圖DiagramError</h1>

<p>@Model.Message</p>

<div class="mermaid">
    @Model.DiagramCode
</div>
<div>
    @Model.StackTrace
</div>

@section Scripts{
    <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
    <script>
        mermaid.initialize({ start"font-size: 12pt;">
}
...

(7)輸出結果

點選Privacy

產出錯誤流程圖

結論

這次用例外錯誤範例來呈現Flowchart對於開發者來說,唯一比較明顯的幫助,大概程式是否有用錯方法或用錯誤物件,從Flowchart會比原來錯誤訊息來的更明確一些。

或許這個範例成效不是那麼明顯,但目前能想到的Mermaid可應用範圍,是那些不容易從文字訊息了解內容,如果可以透過轉換成簡單的圖示,是不是就可以幫助使用者更快速地了解系統產出的文字內容。

 

(8)參考文獻

mermaid github

劉仁竹