使用 Markdown 进行有效日志记录






4.97/5 (48投票s)
MarkdownLog 简介 - 一个开源的 .NET 库,旨在生成 Markdown 格式的日志文件
作为我的一部分
Markdown 格式
在过去 10 年里,Markdown 格式的流行度迅速增长。它最初由 John Gruber 设计,作为一种快速生成 HTML 标记的方法,后来在开发社区中传播开来,成为生成在线内容的流行标准。
我第一次听说 Markdown 是在 Jeff Atwood 的 博文 中,当时他正在构建 StackOverflow。从那时起,出现了许多使用该格式的应用和网页,包括 GitHub、Discourse,以及一些优秀的编辑器,如 iA Writer、MarkdownPad 和基于浏览器的 Dillinger。
不难看出 Markdown 流行起来的原因。比较一下原始 HTML 和 Markdown 的区别
Markdown 代码不仅更容易编写,也更容易阅读。而且,它可以使用众多解析器或应用程序轻松转换为 HTML。我正在使用其中一个应用程序 MarkdownPad 来编写整篇文章。
生成 Markdown 格式的测试结果
我为 iOS 开发了一款 免费的购物清单应用程序。市面上有很多应用程序可以帮助您记住在超市要买什么,但我认为当找不到一个符合我期望的应用程序时,世界就需要另一个购物清单应用程序了。我想要一个购物清单应用程序,可以在我打字时提供建议,并根据超市布局自动排列我的购物清单。
编写测试套件
根据第一版的用户反馈,我最近对该应用程序的建议和分类引擎进行了一些重大更改。
在进行更改之前,我构建了一个测试套件,可以在开发过程中运行,以检查我是否损坏了任何东西或影响了性能——基本上是为了保护应用程序免受我自己的失误。
单元测试
一般来说,单元测试会测试一个非常具体的行为,并返回 PASS
/FAIL
结果。
例如,
Assert.AreEqual(3, Math.Sqrt(9)); Assert.AreEqual(Double.NaN, Math.Sqrt(-1));
单元测试非常适合测试具有明确定义的行为的单个方法和类。我在套件中添加了大量的单元测试,但为了获得我渴望的额外信心,我还需要皮下测试。
皮下集成测试
一个 皮下测试 用于显示应用程序在用户界面下方一个级别(就在其“皮肤”下)的表现和行为。
这些测试需要在真实设备上操作真实数据,以模拟用户体验。每个测试将包含一系列步骤,对应于用户与设备之间的一系列交互。
例如,
- 用户输入
br
- 预期
bread
会出现在建议列表中 - 用户从建议列表中选择
bread
- 用户进入购物模式
- 预期
bread
会被归类到Bakery
(烘焙坊)类别
为了简化这些测试的编写,我构建了一个框架,以便可以使用流畅的代码风格简洁地编写测试。这是使用与应用程序相同的工具——C#、.NET 和 Xamarin——编写的,这意味着它可以直接在 iPhone 上运行并产生准确的真实世界计时。
这是一个完成的测试示例
tests.Run(app => app .Type("2 small onions").PressEnterKey() .Type("5 kilos of potatoes").PressEnterKey() .Type("br") .ExpectThat(x => x.SuggestionsInclude("bread")) .SelectSuggestion("bread") .StartShopping() .ExpectThat(x => x.CategoryContains("Bakery", new[] {"bread"})) .ExpectThat(x => x.CategoryContains("Fresh Fruit, Veg & Flowers", new[] { "2 small onions", "5 kilos of potatoes" })), synopsis: "Can select suggestion and categorise");
随着测试的进行,它会记录每个用户操作,以及相关的观察结果、结果和计时信息。
这就是 Markdown 的用武之地。
作为我的测试框架的一部分,我构建了一个名为 MarkdownLog 的库,我现在已根据 MIT 许可证 开源。该库旨在使从应用程序的数据结构生成 Markdown 格式的文本变得轻而易举。只需一行代码,即可将 .NET 对象集合输出为表格或列表。而且,由于输出是 Markdown,因此以后可以根据需要将其转换为 HTML 进行发布。
这是来自测试运行的 Markdown 格式日志的摘录
Summary of Test Suite Run ========================= Suite completed on 17 July 2014 at 09:52:04 (UTC) 3 tests (223 actions) passed in **422** ms Result | Expected Behaviour | Actions | Time Taken ------:| ------------------------------------------- | -------:| ----------: PASS | Can select suggestion and categorise | 36 | 199 PASS | Common products are included in suggestions | 75 | 153 PASS | Common brands are included in suggestions | 112 | 80 Environment =========== Key | Value ----------------- | ------------------------------------ HardwareModel | iPhone SystemName | iPhone OS SystemVersion | 7.1 AppVersion | 1.01 DeviceIdForVendor | --- Test 1: Can select suggestion and categorise ============================================ Completed in **199** ms *Note: Some output has been removed from here for brevity* Action: Type 'b' ---------------- Suggestions are shown: ------------- | bread | | beans | | breakfast > | ------------- Action: Type 'r' ---------------- Suggestions are shown: ------------- | bread | | breakfast > | | British > | ------------- Check: Suggestions list should include 'bread' ---------------------------------------------- PASS: 'bread' was present in the list of suggestions Action: Choose 'bread' from suggestions list -------------------------------------------- The shopping list now contains: ----------------------- | 2 small onions | | 5 kilos of potatoes | | bread | >| | ----------------------- Action: Enter 'Shopping' mode ----------------------------- Shopping mode has been entered. Items have been categorised: -------------------------- |Fresh Fruit, Veg & Flowers| |--------------------------| | [ ] 2 small onions | | [ ] 5 kilos of potatoes | |--------------------------| |Bakery | |--------------------------| | [ ] bread | -------------------------- Check: Section 'Bakery' should contain 'bread' ---------------------------------------------- PASS: 'bread' was found in 'Bakery' section Check: Section 'Fresh Fruit, Veg & Flowers' should contain: '2 small onions', '5 kilos of potatoes' --------------------------------------------------------------------------------------------------- PASS: '2 small onions' was found in 'Fresh Fruit, Veg & Flowers' section PASS: '5 kilos of potatoes' was found in 'Fresh Fruit, Veg & Flowers' section Test 1 Timings (ms) =================== Typing '2' |### 3 Typing ' ' |### 3 Typing 's' |### 3 Typing 'm' |###### 6 Typing 'a' |################################ 32 Typing 'l' |##### 5 Typing 'l' |# 1 Typing ' ' |## 2 Typing 'o' |## 2 Typing 'n' |### 3 Typing 'i' |######### 9 Typing 'o' |## 2 Typing 'n' |# 1 Typing 's' |############### 15 Typing '5' | 0 Typing ' ' |# 1 Typing 'k' |### 3 Typing 'i' |# 1 Typing 'l' |######## 8 Typing 'o' |## 2 Typing 's' | 0 Typing ' ' |# 1 Typing 'o' |### 3 Typing 'f' |#### 4 Typing ' ' |# 1 Typing 'p' |#### 4 Typing 'o' |### 3 Typing 't' |##### 5 Typing 'a' |### 3 Typing 't' |# 1 Typing 'o' |# 1 Typing 'e' |######################################### 41 Typing 's' |## 2 Typing 'b' |# 1 Typing 'r' |# 1 Categorising items |################### 19 ------------------------------------------ Overall Slowest Timings ======================= Note: timings are classified based on human perception metrics from [Jacob Nielson's article](http://www.nngroup.com/articles/powers-of-10-time-scales-in-ux/): * Anything that takes less than 0.1 seconds feels IMMEDIATE - as if the user is directly causing something to happen. * Between 0.1 and 1 second is quick enough to give the user MAINTAINED-FOCUS and feel in control. * Longer than 1 second but less than 10 seconds is acceptable for MAINTAINED-ATTENTION, but the user may begin to feel impatient. * Generally, BROKEN-FLOW, will occur after 10 seconds and the user will context switch and do something else. Test | Action | Perception | Time (ms) ------------------------------------------- | ------------------ | ---------- | --------- Can select suggestion and categorise | Typing 'e' | IMMEDIATE | 41 Can select suggestion and categorise | Typing 'a' | IMMEDIATE | 32 Common products are included in suggestions | Typing 'g' | IMMEDIATE | 28 Can select suggestion and categorise | Categorising items | IMMEDIATE | 19 Common products are included in suggestions | Typing 'c' | IMMEDIATE | 19 Common products are included in suggestions | Typing 'c' | IMMEDIATE | 19 Can select suggestion and categorise | Typing 's' | IMMEDIATE | 15 Common products are included in suggestions | Typing 'w' | IMMEDIATE | 13 Common products are included in suggestions | Typing 'b' | IMMEDIATE | 12 Common products are included in suggestions | Typing 'b' | IMMEDIATE | 10
如您所见,Markdown 格式的日志在其原始形式下非常易读,因此输出可以写入控制台并立即查看,无需特殊的查看器或转换器。
MarkdownLog
MarkdownLog 设计得轻巧且易于使用。它是一个 .NET 可移植类库 (PCL),除 .NET Framework 外,没有任何依赖项。
入门
- 从构建服务器下载 MarkdownLog.dll。
- 在您的项目中添加对 MarkdownLog.dll 的引用。
- 在文件顶部添加
using MarkdownLog;
。 - 从您的数据生成 Markdown 输出。
MarkdownLog 可以生成 Markdown 规范 中描述的所有元素。
从集合创建列表
var myStrings = new[] { "John", "Paul", "Ringo", "George" }; Console.Write(myStrings.ToMarkdownBulletedList());
这将产生
* John * Paul * Ringo * George
集合可以是任何类型,不只是字符串。会调用每个对象的 ToString
方法来生成列表项的文本。您也可以指定一个函数,如下所示:
var processes = Process.GetProcesses(); Console.Write(processes.ToMarkdownBulletedList(i => i.ProcessName));
如果您更喜欢编号列表,请改用 ToMarkdownNumberedList
扩展方法。
var files = new DirectoryInfo(@"C:\MarkdownLog").EnumerateFiles(); Console.Write(files.ToMarkdownNumberedList(i => i.Name + " is " + i.Length + " bytes"));
这将产生
1. appveyor.yml is 302 bytes 2. LICENSE is 1088 bytes 3. MarkdownLog.sln is 1480 bytes 4. README.md is 6173 bytes
创建表格
可以使用 ToMarkdownTable
扩展方法将数据集合输出为 GitHub Flavored Markdown 表格。
var data = new[] { new{Name = "Meryl Streep", Nominations = 18, Awards=3}, new{Name = "Katharine Hepburn", Nominations = 12, Awards=4}, new{Name = "Jack Nicholson", Nominations = 12, Awards=3} }; Console.Write(data.ToMarkdownTable());
这将产生
<code> Name | Nominations | Awards ----------------- | -----------:| ------: Meryl Streep | 18 | 3 Katharine Hepburn | 12 | 4 Jack Nicholson | 12 | 3 </code>
列会根据数据类型自动对齐。文本左对齐,数字右对齐。
可以使用方法重载来指定要包含的列以及如何选择其数据。只需为需要的每个列传递一个函数。
var tableWithHeaders = data .ToMarkdownTable(i => i.Name, i => i.Nominations + i.Awards) .WithHeaders("Name", "Total"); Console.Write(tableWithHeaders);
这将产生
<code> Name | Total ----------------- | -----: Meryl Streep | 21 Katharine Hepburn | 16 Jack Nicholson | 15 </code>
创建条形图
可以使用一组标签和数字创建条形图。
var worldCup = new Dictionary<string, int> { {"Brazil", 5}, {"Italy", 4}, {"Germany", 4}, {"Argentina", 2}, {"Uruguay", 2}, {"France", 1}, {"Spain", 1}, {"England", 1} }; Console.Write(worldCup.ToMarkdownBarChart());
这会渲染为 Markdown 代码块。
Brazil |##### 5 Italy |#### 4 Germany |#### 4 Argentina |## 2 Uruguay |## 2 France |# 1 Spain |# 1 England |# 1 ------
条形图支持负数和浮点数,以及值的缩放。
Cos(0.0) |#################### 1 Cos(0.3) |################### 0.95 Cos(0.6) |################ 0.81 Cos(0.9) |############ 0.59 Cos(1.3) |###### 0.31 Cos(1.6) | 0 Cos(1.9) ######| -0.31 Cos(2.2) ############| -0.59 Cos(2.5) ################| -0.81 Cos(2.8) ###################| -0.95 Cos(3.1) ####################| -1 Cos(3.5) ###################| -0.95 Cos(3.8) ################| -0.81 Cos(4.1) ############| -0.59 Cos(4.4) ######| -0.31 Cos(4.7) | 0 Cos(5.0) |###### 0.31 Cos(5.3) |############ 0.59 Cos(5.7) |################ 0.81 Cos(6.0) |################### 0.95 -----------------------------------------
创建标题
Console.Write("The Origin of the Species".ToMarkdownHeader());
这将产生
The Origin of the Species =========================
创建子标题
Console.Write("By Means of Natural Selection".ToMarkdownSubHeader());
这将产生
By Means of Natural Selection -----------------------------
创建自动换行的段落
var text = "Lolita, light of my life, fire of my loins." + "My sin, my soul. Lo-lee-ta: the tip of the tongue taking a trip of" + "three steps down the palate to tap, at three, on the teeth. Lo. Lee. Ta."; Console.Write(text.ToMarkdownParagraph());
这将产生
Lolita, light of my life, fire of my loins. My sin, my soul. Lo-lee-ta: the tip of the tongue taking a trip of three steps down the palate to tap, at three, on the teeth. Lo. Lee. Ta.
默认情况下,段落会在第 80 列自动换行,但这可以进行配置。
创建块引用
const string text = "There are only two hard things in computer science:\n" + "cache invalidation,\n" + "naming things,\n" + "and off-by-one errors."; Console.Write(text.ToMarkdownBlockquote());
这将产生
> There are only two hard things in computer science: > cache invalidation, > naming things, > and off-by-one errors.
块引用也可以包含其他 Markdown 元素。例如:
var blockQuote = new Blockquote(); blockQuote.Append(new HorizontalRule()); blockQuote.Append(new Header("COMPUTING MACHINERY AND INTELLIGENCE")); blockQuote.Append(new SubHeader("By A. M. Turing.")); blockQuote.Append(new Paragraph("...")); blockQuote.Append(new Paragraph("The idea behind digital computers" + "may be explained by saying that these machines are intended to carry" + "out any operations which could be done by a human computer. The human" + "computer is supposed to be following fixed rules; he has no authority" + "to deviate from them in any detail. We may suppose that these rules" + "are supplied in a book, which is altered whenever he is put on to a" + "new job. He has also an unlimited supply of paper on which he does his" + "calculations. He may also do his multiplications and additions on a" + "\"desk machine,\" but this is not important."));" blockQuote.Append(new Paragraph("If we use the above explanation as" + "a definition we shall be in danger of circularity of argument. We avoid" + "this by giving an outline. of the means by which the desired effect is" + "achieved. A digital computer can usually be regarded as consisting of three parts:")); blockQuote.Append(new NumberedList("Store", "Executive unit", "Control")); Console.Write(blockQuote);
这将产生
> -------------------------------------------------------------------------------- > > COMPUTING MACHINERY AND INTELLIGENCE > ==================================== > > By A. M. Turing. > ---------------- > > ... > > The idea behind digital computers may be explained by saying that these > machines are intended to carry out any operations which could be done by a > human computer. The human computer is supposed to be following fixed rules; he > has no authority to deviate from them in any detail. We may suppose that these > rules are supplied in a book, which is altered whenever he is put on to a new > job. He has also an unlimited supply of paper on which he does his > calculations. He may also do his multiplications and additions on a "desk > machine," but this is not important. > > If we use the above explanation as a definition we shall be in danger of > circularity of argument. We avoid this by giving an outline. of the means by > which the desired effect is achieved. A digital computer can usually be > regarded as consisting of three parts: > > 1. Store > 2. Executive unit > 3. Control >
通过写入容器来延迟输出
到目前为止,我们一直将每个元素直接写入控制台窗口。这对于在应用程序运行时将特定数据结构的快照输出到输出窗口很有用。但是,要生成可以保存到磁盘的完整日志,应将每个元素写入 MarkdownContainer
。
var log = new MarkdownContainer(); var countries = new[]{"Zimbabwe", "Italy", "Bolivia", "Finland", "Australia"}; log.Append("Countries (unsorted)".ToMarkdownHeader()); log.Append(countries.ToMarkdownNumberedList()); var sorted = countries.OrderBy(i => i); log.Append("Countries (sorted)".ToMarkdownHeader()); log.Append(sorted.ToMarkdownNumberedList()); Console.Write(log);
这将产生
Countries (unsorted) ==================== 1. Zimbabwe 2. Italy 3. Bolivia 4. Finland 5. Australia Countries (sorted) ================== 1. Australia 2. Bolivia 3. Finland 4. Italy 5. Zimbabwe
如果您想了解更多关于 MarkdownLog 的信息,请访问 项目主页,在那里您会找到更多示例以及指向最新源代码的链接。如果您有兴趣贡献,我将非常乐意听取您的意见!
而且,如果您正在为 iPhone 寻找一款免费的应用程序,可以节省您每周购物的时间,请看一下 Shopping UK,并告诉我您的想法 :)