探討在沒有使用任何一種 Log 套件下,想要做到系統活動的設計做法
當在進行任何軟體專案開發的時候,在開發階段,對於所設計的專案程式碼有任何疑慮,或者所執行的結果不正確的時候,隨時可以透過 IDE 工具來設定一個中斷點,一旦程式執行到這個中斷點的時候,就會停止執行,並且進入到中斷點的程式碼區段,這時候可以透過觀察程式碼的變數值,來判斷程式碼的執行是否正確,或者是透過這個中斷點,來進行程式碼的逐步執行,觀察程式碼的執行流程,這些都是在開發階段,可以透過 IDE 工具來進行的操作。另外,也可以透過程式碼來將各種物件資訊,輸出到 Console 上或者使用類似 MessageBox 這樣的 API 來彈跳出一個對話窗,藉此觀察到更多詳細資訊。
可是,當這個軟體系統開發完成之後,這些在開發階段可以透過 IDE 工具來觀察的資訊,就不再適用了,在軟體系統正式上線之後,便無法使用 IDE 這類工具來取得相關資訊;可是,當這個軟體系統運作不如預期或者發生了異常行為,身為開發者要如何解讀與找出真正發生的原因呢?
這時候,就需要透過 Log 這樣的機制來記錄系統的活動,這些活動的資訊,可以透過 Log 這樣的機制,將這些資訊寫入到檔案內,或者是寫入到資料庫內,這樣的做法,就可以讓該軟體開發者或者一線維運工程師,可以透過這些 Log 資訊,來觀察系統的活動,進而判斷或者解讀,甚至能夠找出當時系統發生了甚麼問題。
然而,對於要提供那些機制或者要將資料寫到哪裡,對於系統開發而言,一旦這些都是需求要由開發者自行開發出來,此時,就需要面對如何設計出一個好的 Log 的機制,這個機制的設計,就是要讓系統開發者可以透過這個機制,來將系統的活動資訊,寫入到某個地方,這個地方可以是檔案,也可以是資料庫,或者遠端的 Web API,甚至是其他的地方,看到這裡,相信你也會覺得這是一個相當負責且不容易做到的事情。
然而,對於軟體設計師而言,對於這樣的狀況都是覺得自己最厲害、自己最棒棒,凡事自己動手做,才能夠凸顯出自己的厲害, 殊不知這樣的決定將會造成更多的災害與更多問題出來。
由於這是一個系列文章,主要是要探討 Log 的機制的使用與設計,所以,就會有這篇開始文章,從一切錯誤的決定或者自以為視判斷開始,了解到前人的經驗與貢獻有多麼的重要,徹底了解重新打造輪胎這樣的原則之背後意義。
在這篇文章中,將會探討在沒有使用任何一種 Log 套件下,想要做到系統活動的設計做法,這裡所說的沒有使用任何一種 Log 套件,是指的不使用任何一種第三方的 Log 套件,而是自行設計一個 Log 的機制,這樣的做法,可以讓讀者了解到 Log 的機制是如何運作的,進而可以更加了解到 Log 的機制。
建立自行發明的寫入日誌之類別服務的測試專案
請依照底下的操作,建立起這篇文章需要用到的練習專案
- 打開 Visual Studio 2022 IDE 應用程式
- 從 [Visual Studio 2022] 對話窗中,點選右下方的 [建立新的專案] 按鈕
- 在 [建立新專案] 對話窗右半部
- 切換 [所有語言 (L)] 下拉選單控制項為 [C#]
- 切換 [所有專案類型 (T)] 下拉選單控制項為 [主控台]
- 在中間的專案範本清單中,找到並且點選 [主控台應用程式] 專案範本選項
專案,用於建立可在 Windows、Linux 及 macOS 於 .NET 執行的命令列應用程式
- 點選右下角的 [下一步] 按鈕
- 在 [設定新的專案] 對話窗
- 找到 [專案名稱] 欄位,輸入
csLog01
作為專案名稱 - 在剛剛輸入的 [專案名稱] 欄位下方,確認沒有勾選 [將解決方案與專案至於相同目錄中] 這個檢查盒控制項
- 點選右下角的 [下一步] 按鈕
- 現在將會看到 [其他資訊] 對話窗
- 在 [架構] 欄位中,請選擇最新的開發框架,這裡選擇的 [架構] 是 :
.NET 7.0 (標準字詞支援)
- 在這個練習中,需要去勾選 [不要使用最上層陳述式(T)] 這個檢查盒控制項
這裡的這個操作,可以由讀者自行決定是否要勾選這個檢查盒控制項
- 請點選右下角的 [建立] 按鈕
稍微等候一下,這個主控台專案將會建立完成
自行設計寫入日誌的功能
- 在專案中找到與打開 [Program.cs] 這個檔案
- 使用底下 C# 程式碼取代原始的程式碼
```csharp
namespace csLog01
{
/// <summary>
/// 不要發明輪子,自己做 Log 機制
/// 在底下的做法,你看到了甚麼問題?
/// </summary>
internal class Program
{
public static Logger logger = new Logger();
static void Main(string[] args)
{
logger.Trace("我是追蹤:Trace");
}
}
public class Logger
{
public void Trace(string message)
{
#region 想要將日誌訊息寫入螢幕
Console.WriteLine(message);
#endregion
#region 想要將日誌訊息寫入到檔案內
string filePath = "example.txt";
using (StreamWriter writer = new StreamWriter(filePath, true))
{
writer.Write(message + Environment.NewLine);
}
#endregion
}
}
}
在 Logger 類別內的 Trace 方法內,將會接收到一個字串參數,這個 message 參數,將會是日誌內容,當要把這個 message 寫入到檔案內的時候,該檔案的名稱與路徑,將會是寫死在這個類別內,無法透過外部檔案或者設定來變更,當然,這樣的日誌類別就會顯得很沒有彈性。
任何程式設計師當有這樣日誌需求的時候,大家都會想到的是,這一個非常簡單的工作,那不就是把程式碼中的物件或者當時的狀態,輸出到任何一個裝置上,例如:螢幕、檔案內,好的,打完,收工。
越是簡單的工作,就會存在著更多的風險與問題,若選擇要將各種日誌內容寫入到螢幕上,此時,當在開發階段,程式設計師在自己點腦上來執行這個系統,當然可以在自己的螢幕上看到這些日誌內容,然而,若這個系統在正式 Production 環境上運行的時候,若將這些內容寫入到螢幕上呢?這時候,就需要透過遠端連線的方式,來連線到 Production 環境的機器上,然後再透過某種方式,來觀察這些日誌內容,這樣的做法,就會造成更多的問題,例如:在 Production 環境上,這些日誌內容,可能會被其他人看到,這樣的做法,就會造成資訊安全的問題。最重要的是,當有問題發生的時候,如何取得之前某個時間點發生的日誌內容,因為,當開發者收到這樣問題回報的時候,這些日誌內容,就會被覆蓋掉,甚至沒有永久保存的問題,這樣的做法,就會造成無法追蹤問題的發生原因。
不管如何,在這篇文章中,將會假設一個程式設計師,想要硬做出這樣日誌需求的功能,會經歷要寫出那些程式碼,這會有助於之後的系列文章,知道市面上的各種日誌工具或者開發套件的價值。
為了要提供日誌這樣的服務,當然要先設計一個類別,這個類別就是要提供日誌服務的類別,這個類別的名稱,就叫做 Logger
,這個類別內,將會提供一個 Trace
方法,這個方法的功能就是要將日誌訊息寫入到螢幕上,這個方法的實作,就是透過 Console.WriteLine
這個 API 來將日誌訊息寫入到螢幕上,另外,也可以將這些日誌內容同時也要寫入到檔案內。
另外,從這裡也看到其他的問題,首先,就是這個類別沒有一個抽象介面存在,這將會造成未來需求擴增與調整的問題,這裡將會無法符合 SOLID 物件導向程式設計原則指引中的提到的OCP Open Closed Principle 開放封閉原則 ,造成這樣的問題,將會導致未來會需要修改到這個類別的程式碼,使得造成這個類別的程式碼,造成不穩定問題,這樣的做法,將會造成這個類別的程式碼,將會不易於維護。
另外,從 [Trace] 這個方法中,也看到這裡違反了 SPR Single Responsibility Principle ,因為在這個方法內同時具備了兩個責任,一個要寫入到螢幕上,一個要寫入日誌內容到檔案內,這也同時會使得這樣的程式碼會很難維護。
除了上述提到的兩個設計原則問題,還存在著更多的問題,其中一個就是執行緒安全,這個問題將會於當有多個執行緒需要寫入日誌內容的時候,造成問題,或者因為檔案的存取權限問題,造成無法寫入到檔案內,這些都是需要考量的問題。
因為,只是一個需求,可以先看看市面上都有著各種的 Log 套件,這些軟體都已經經過時間與社群的考驗,這樣的軟體一定比起自己開發起來的 Log 套件,要來的更加的穩定與可靠,這樣的軟體,也一定會有更多的功能,例如:可以將日誌內容寫入到資料庫內,或者是可以將日誌內容寫入到遠端的 Web API 內,這些都是自己開發起來的 Log 套件,所無法做到的事情。
不管如何,這裡還是把這個日誌類別與服務都設計出來了,使用起來也相當的方便,首先建立一個 Logger 類別,這裡使用 public static Logger logger = new Logger()
這個敘述,一旦取得這個物件,便可以使用這樣的方式 logger.Trace("我是追蹤:Trace")
將日誌內容寫入到螢幕與檔案上。
沒有留言:
張貼留言