我長期以來一直習慣用Elmah來紀錄錯誤日誌(但沒有用elmah.io),而它本身雖然有提供檢視頁面,但功能卻十分基本,甚至連搜索功能都沒有,既然如此,今天就來自己寫一個吧。
學習目標 React : 一直沒有時間學,終於找到這次的機會了,就結合.Net Core的React專案一起來研究吧 Material-UI : 畫面的部份當然也要找個React的知名UI Component來減少設計時間 簡單的搜尋功能 : 我想要為Elmah加入的功能 架構 整個環境大致如下圖。
假設有多個站台,發生錯誤時將資訊記錄到DB,而要重作畫面的話就必須重寫存取這些資料的邏輯以及畫面,也就是圖片右半部的WebApi與React View,而這部分我們可以直接使用Vsiaul Studio提供的.Net Core React Project來完成。
讀取錯誤日誌DB 這裡用EntityFramework來做到簡易的串接DB,跟上面架構圖比較不同的是,因為我自己維護的錯誤日誌散落在不同架構的DB內(SqlServer、Postgres),所以這裡多了一層Factory來幫助我建立不同DB的Context。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class ElmahDbContextFactory : IDesignTimeDbContextFactory<ElmahDbContext> { public ElmahDbContext CreateDbContext (string [] args ) { var dataSource = args[0 ]; var catalog = args[1 ]; var userId = args[2 ]; var password = args[3 ]; var provider = args[4 ]; var optionsBuilder = new DbContextOptionsBuilder<ElmahDbContext>(); if (DatabaseProviderTypes.SqlServer.ToString() == provider) { optionsBuilder.UseSqlServer($"Data Source={dataSource} ;Initial Catalog={catalog} ;User ID={userId} ;Password={password} ;multipleactiveresultsets=True;" ); } if (DatabaseProviderTypes.PostgresQL.ToString() == provider) { optionsBuilder.UseNpgsql($"Server={dataSource} ;Database={catalog} ;User Id={userId} ;Password={password} ;" ); } return new ElmahDbContext(optionsBuilder.Options); } }
接下來實作查詢錯誤日誌列表的Api,這裡可以用剛剛的Factory產生Context讀取錯誤日誌,除了能搜尋ErrorMessage之外,我還想要有分頁功能,所以Model中加入了一些必要參數。
1 2 3 4 5 6 7 8 9 10 public class ReceiveGetLogsModel { public int EntityId { get ; set ; } public int Limit { get ; set ; } public int CurrentPage { get ; set ; } public string SearchMessage { get ; set ; } }
1 2 3 4 5 6 public class ResponseGetLogsModel { public List<ResponseGetLogEntity> Datas { get ; set ; } public int Count { get ; set ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 [HttpPost("[action]" ) ] public ResponseGetLogsModel GetLogs ([FromBody]ReceiveGetLogsModel model ) { var entity = Db.Elmahs.FirstOrDefault(s => s.Id == model.EntityId); var factory = new ElmahDbContextFactory(); var elmah = factory.CreateDbContext(new string [] { entity.DataSource, entity.CataLog, entity.UserId, entity.Password, entity.ProviderType.ToString() }); var query = elmah.Errors.AsQueryable(); if (!string .IsNullOrEmpty(model.SearchMessage)) { query = query.Where(s => s.Message.Contains(model.SearchMessage)); } var count = query.Count(); var pageDatas = query.Skip((model.CurrentPage - 1 ) * model.Limit) .Take(model.Limit) .Select(s => new ResponseGetLogEntity() { ErrorId = s.ErrorId, Application = s.Application, Host = s.Host, Message = s.Message, Sequence = s.Sequence, Source = s.Source, StatusCode = s.StatusCode, TimeUtc = s.TimeUtc, Type = s.Type, User = s.User }) .ToList(); return new ResponseGetLogsModel() { Datas = pageDatas, Count = count }; }
這樣就簡單的完成該功能了,實際用Postman測試一下看是否運作正常。
設計畫面 Material-UI官方推薦了一些不錯的Theme ,這裡也是直接選擇免費的Material Dashboard ,下載完後把相關東西放進我們的.Net Core專案的ClientApp
目錄下,應該就可以正常執行了。
把專案站台啟動起來,應該可以看到Preview中的漂亮Dashboard,React專案中也提供了許多現成的View來教開發者如何使用它們提供的UI Component,稍微看一下後就可以開始修改和去除我們不需要的東西,大致結果如下圖。
串接資料 前端的大致框架搞定後,接下來要把資料接回來並顯示在畫面上,這裡可以順道學習一下Redux,建立Store來處理這件事情。
state
1 2 3 4 5 6 7 8 9 10 11 12 const requestGetLogs = 'REQUEST_GET_LOGS' ;const receiveGetLogs = 'RECEIVE_GET_LOGS' ;const initialState = { logs: { count: 0 , currentPage: 1 , searchMessage: '' , limit: 10 , data: [] }, isLoading: false };
action
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 export const actionCreators = { requestGetLogs: (currentPage = 1 , limit = 10 , searchMessage = '' ) => async (dispatch, getState) => { dispatch({ type : requestGetLogs }); const url = '/api/Elmah/GetLogs' ; const response = await fetch(url, { method: 'POST' , body: JSON .stringify({ EntityId: getState().elmah.activeEntityId, CurrentPage: currentPage, Limit: limit, SearchMessage: searchMessage }), headers: { 'content-type' : 'application/json' } }); const data = await response.json(); dispatch({ type: receiveGetLogs, datas: data.datas, count: data.count, currentPage: currentPage, searchMessage: searchMessage, limit: limit }); } };
reducer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 export const reducer = (state, action ) => { state = state || initialState; if (action.type === requestGetLogs) { return { ...state, logs: { data: [] }, isLoading: true }; } if (action.type === receiveGetLogs) { return { ...state, logs: { data: action.datas, currentPage: action.currentPage, limit: action.limit, count: action.count, searchMessage: action.searchMessage }, isLoading: false }; } return state; };
再將State和Dispatche綁到我們自己寫的View Component上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Logs extends React .Component { componentWillMount() { let page = this .props.match.params.page || 1 ; let limit = this .props.match.params.limit || 10 ; let message = this .props.match.params.message || '' ; this .props .requestGetLogs(parseInt (page), parseInt (limit), message); } } const mapStateToProps = state => ({elmah : state.elmah});const mapDispatchToProps = dispatch => bindActionCreators(actionCreators, dispatch);export default connect(mapStateToProps, mapDispatchToProps)(Logs);
資料就可以正常顯示了。
詳情頁我們也可以依樣畫葫蘆,這樣近似於原本Elmah的功能就完成了。
至於搜尋功能,包含API和畫面都是我們自己做的,未來擴充也相當方便。
結語 所有學習目標應該都完成了,目前Angular、Vue、React通通都寫過,個人覺得React相較於前兩者真的是有比較難一點,而這個專案未來我也會抽時間繼續開發,希望有機會能幫到同為使用Elmah的大家。