Skip to content

Commit cd52c01

Browse files
committed
feat(日志): 添加控制台日志格式化显示功能
实现 LogConsole 类提供控制台日志的美观格式化显示,包括带边框的标题、配置信息框和分隔线 添加 LogHelper.Console 部分类作为控制台日志功能的入口 支持自定义框架宽度和自动处理中英文混合文本的居中对齐
1 parent 04587bd commit cd52c01

2 files changed

Lines changed: 400 additions & 0 deletions

File tree

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
// ==========================================================================================
2+
// GameFrameX 组织及其衍生项目的版权、商标、专利及其他相关权利
3+
// GameFrameX organization and its derivative projects' copyrights, trademarks, patents, and related rights
4+
// 均受中华人民共和国及相关国际法律法规保护。
5+
// are protected by the laws of the People's Republic of China and relevant international regulations.
6+
//
7+
// 使用本项目须严格遵守相应法律法规及开源许可证之规定。
8+
// Usage of this project must strictly comply with applicable laws, regulations, and open-source licenses.
9+
//
10+
// 本项目采用 MIT 许可证与 Apache License 2.0 双许可证分发,
11+
// This project is dual-licensed under the MIT License and Apache License 2.0,
12+
// 完整许可证文本请参见源代码根目录下的 LICENSE 文件。
13+
// please refer to the LICENSE file in the root directory of the source code for the full license text.
14+
//
15+
// 禁止利用本项目实施任何危害国家安全、破坏社会秩序、
16+
// It is prohibited to use this project to engage in any activities that endanger national security, disrupt social order,
17+
// 侵犯他人合法权益等法律法规所禁止的行为!
18+
// or infringe upon the legitimate rights and interests of others, as prohibited by laws and regulations!
19+
// 因基于本项目二次开发所产生的一切法律纠纷与责任,
20+
// Any legal disputes and liabilities arising from secondary development based on this project
21+
// 本项目组织与贡献者概不承担。
22+
// shall be borne solely by the developer; the project organization and contributors assume no responsibility.
23+
//
24+
// GitHub 仓库:https://github.com/GameFrameX
25+
// GitHub Repository: https://github.com/GameFrameX
26+
// Gitee 仓库:https://gitee.com/GameFrameX
27+
// Gitee Repository: https://gitee.com/GameFrameX
28+
// 官方文档:https://gameframex.doc.alianblank.com/
29+
// Official Documentation: https://gameframex.doc.alianblank.com/
30+
// ==========================================================================================
31+
32+
using GameFrameX.Foundation.Extensions;
33+
34+
namespace GameFrameX.Foundation.Logger;
35+
36+
/// <summary>
37+
/// 控制台日志显示辅助类
38+
/// </summary>
39+
/// <remarks>
40+
/// <para>
41+
/// 提供了一系列方法用于在控制台中以美观的框架格式显示标题、配置信息和分隔线。
42+
/// 支持自定义框架宽度,并能够自动处理文本居中对齐和字符宽度计算。
43+
/// </para>
44+
/// <para>
45+
/// 主要功能包括:
46+
/// <list type="bullet">
47+
/// <item><description>显示带双线边框的大标题(支持主标题和最多两个子标题)</description></item>
48+
/// <item><description>显示带标题的配置信息框</description></item>
49+
/// <item><description>显示分隔线(可选择包含标题文本)</description></item>
50+
/// <item><description>自定义框架宽度设置</description></item>
51+
/// <item><description>自动处理中英文混合文本的居中对齐</description></item>
52+
/// </list>
53+
/// </para>
54+
/// <para>
55+
/// 使用示例:
56+
/// <code>
57+
/// var console = new LogConsole();
58+
/// console.SetFrameLength(80);
59+
/// console.ShowMaxTitle("系统启动", "GameFrameX", "v1.0.0");
60+
/// console.ShowOption("配置文件", "config.json");
61+
/// console.ShowLineTitle("初始化完成");
62+
/// </code>
63+
/// </para>
64+
/// </remarks>
65+
internal sealed class LogConsole
66+
{
67+
/// <summary>
68+
/// 输出框线的长度,默认为76个字符
69+
/// </summary>
70+
private int _frameLength = 76;
71+
72+
/// <summary>
73+
/// 设置输出框架的宽度
74+
/// </summary>
75+
/// <param name="length">框架宽度,必须大于0。建议设置为终端宽度或合适的显示宽度(如80、100等)</param>
76+
/// <exception cref="ArgumentOutOfRangeException">当 <paramref name="length"/> 小于等于0时抛出</exception>
77+
/// <remarks>
78+
/// 框架宽度影响所有输出方法的显示效果,包括标题居中对齐和边框绘制。
79+
/// 设置过小的宽度可能导致文本显示异常,建议最小宽度为20个字符。
80+
/// </remarks>
81+
public void SetFrameLength(int length)
82+
{
83+
// 验证参数有效性,确保框架宽度为正数
84+
ArgumentOutOfRangeException.ThrowIfGreaterThan(0, length, nameof(length));
85+
_frameLength = length;
86+
}
87+
88+
/// <summary>
89+
/// 显示带边框的大标题,支持主标题和最多两个子标题
90+
/// </summary>
91+
/// <param name="title">主标题文本,必须提供且不能为null</param>
92+
/// <param name="title2">第一个子标题文本,可选。如果为空字符串、null或仅包含空白字符则不显示</param>
93+
/// <param name="title3">第二个子标题文本,可选。如果为空字符串、null或仅包含空白字符则不显示</param>
94+
/// <remarks>
95+
/// <para>
96+
/// 该方法会创建一个带有双线边框(╔═══╗ 和 ╚═══╝)的标题框。
97+
/// 所有标题文本都会在框架内居中显示,自动处理中英文混合文本的宽度计算。
98+
/// </para>
99+
/// <para>
100+
/// 子标题的显示逻辑:
101+
/// <list type="number">
102+
/// <item><description>主标题始终显示</description></item>
103+
/// <item><description>只有当title2不为空时才显示第一个子标题</description></item>
104+
/// <item><description>只有当title3不为空时才显示第二个子标题</description></item>
105+
/// <item><description>子标题按顺序显示,每个标题占用一行</description></item>
106+
/// </list>
107+
/// </para>
108+
/// <para>
109+
/// 输出后会自动添加一个空行以便与后续内容分隔。
110+
/// </para>
111+
/// </remarks>
112+
/// <example>
113+
/// <code>
114+
/// // 显示单个标题
115+
/// console.ShowMaxTitle("系统启动");
116+
///
117+
/// // 显示主标题和一个子标题
118+
/// console.ShowMaxTitle("GameFrameX", "日志系统");
119+
///
120+
/// // 显示完整的三行标题
121+
/// console.ShowMaxTitle("GameFrameX", "日志系统", "v1.0.0");
122+
/// </code>
123+
/// </example>
124+
public void ShowMaxTitle(string title, string title2 = "", string title3 = "")
125+
{
126+
// 生成顶部边框字符串,长度为框架宽度减去左右边框字符
127+
string character = '═'.RepeatChar(_frameLength - 2);
128+
Console.WriteLine($"╔{character}╗");
129+
WriteTitle(title);
130+
131+
// 检查并显示第一个子标题
132+
if (title2.IsNotNullOrEmptyOrWhiteSpace())
133+
{
134+
WriteTitle(title2);
135+
}
136+
137+
// 检查并显示第二个子标题
138+
if (title3.IsNotNullOrEmptyOrWhiteSpace())
139+
{
140+
WriteTitle(title3);
141+
}
142+
143+
// 输出底部边框并添加空行分隔
144+
Console.WriteLine($"╚{character}╝");
145+
Console.WriteLine();
146+
}
147+
148+
/// <summary>
149+
/// 在框架内居中显示标题文本
150+
/// </summary>
151+
/// <param name="title">要显示的标题文本,不能为null</param>
152+
/// <exception cref="ArgumentNullException">当 <paramref name="title"/> 为null时抛出</exception>
153+
/// <remarks>
154+
/// <para>
155+
/// 该方法会自动计算文本的显示宽度(考虑中英文字符的不同宽度),
156+
/// 然后在当前设置的框架宽度内进行居中对齐。
157+
/// </para>
158+
/// <para>
159+
/// 居中算法:
160+
/// <list type="number">
161+
/// <item><description>计算文本的实际显示宽度</description></item>
162+
/// <item><description>计算剩余空间并平均分配到左右两侧</description></item>
163+
/// <item><description>如果剩余空间为奇数,在文本后添加一个空格以保持对称</description></item>
164+
/// <item><description>使用║字符作为左右边框</description></item>
165+
/// </list>
166+
/// </para>
167+
/// </remarks>
168+
private void WriteTitle(string title)
169+
{
170+
ArgumentNullException.ThrowIfNull(title, nameof(title));
171+
// 获取文本的实际显示宽度(中文字符宽度为2,英文字符宽度为1)
172+
var stringWidth = title.GetDisplayWidth();
173+
var remaining = _frameLength - stringWidth - 2;
174+
var surplus = remaining % 2;
175+
// 如果剩余空间为奇数,在文本后添加一个空格以保持左右对称
176+
if (surplus > 0)
177+
{
178+
title += " ";
179+
}
180+
181+
remaining /= 2;
182+
string padding = ' '.RepeatChar(remaining);
183+
Console.WriteLine($"║{padding}{title}{padding}║");
184+
}
185+
186+
/// <summary>
187+
/// 显示带标题的配置信息框
188+
/// </summary>
189+
/// <param name="title">配置项标题,不能为null</param>
190+
/// <param name="content">配置内容,将调用ToString()方法显示,不能为null</param>
191+
/// <exception cref="ArgumentNullException">当 <paramref name="title"/> 或 <paramref name="content"/> 为null时抛出</exception>
192+
/// <remarks>
193+
/// <para>
194+
/// 该方法会创建一个带有标题的配置信息显示框,格式如下:
195+
/// <code>
196+
/// ╔═══配置标题═══╗
197+
/// 配置内容
198+
/// ╚═════════════╝
199+
/// </code>
200+
/// </para>
201+
/// <para>
202+
/// 标题会在顶部边框中居中显示,内容直接输出在框架内部。
203+
/// 输出后会自动添加一个空行以便与后续内容分隔。
204+
/// </para>
205+
/// </remarks>
206+
/// <example>
207+
/// <code>
208+
/// console.ShowOption("数据库连接", "Server=localhost;Database=GameFrameX");
209+
/// console.ShowOption("日志级别", LogLevel.Information);
210+
/// </code>
211+
/// </example>
212+
public void ShowOption(string title, object content)
213+
{
214+
ArgumentNullException.ThrowIfNull(title, nameof(title));
215+
ArgumentNullException.ThrowIfNull(content, nameof(content));
216+
// 计算标题的显示宽度并生成居中的顶部边框
217+
int stringWidth = title.GetDisplayWidth();
218+
int remaining = _frameLength - stringWidth - 2;
219+
remaining /= 2;
220+
string padding = '═'.RepeatChar(remaining);
221+
// 生成底部边框,使用完整的等号字符填充
222+
string character = '═'.RepeatChar(_frameLength - 2);
223+
Console.WriteLine($"╔{padding}{title}{padding}╗");
224+
Console.WriteLine(content);
225+
Console.WriteLine($"╚{character}╝");
226+
Console.WriteLine();
227+
}
228+
229+
/// <summary>
230+
/// 显示分隔线,可选择是否包含标题文本
231+
/// </summary>
232+
/// <param name="title">分隔线中的标题文本,为空时显示纯分隔线。不能为null</param>
233+
/// <exception cref="ArgumentNullException">当 <paramref name="title"/> 为null时抛出</exception>
234+
/// <remarks>
235+
/// <para>
236+
/// 该方法用于在控制台输出中创建视觉分隔效果,支持两种模式:
237+
/// </para>
238+
/// <para>
239+
/// 1. 纯分隔线模式(title为空字符串):
240+
/// <code>
241+
/// ═══════════════════════════════════════
242+
/// </code>
243+
/// </para>
244+
/// <para>
245+
/// 2. 带标题的分隔线模式:
246+
/// <code>
247+
/// ═══════════标题文本═══════════
248+
/// </code>
249+
/// </para>
250+
/// <para>
251+
/// 如果标题文本过长(超出框架宽度-4个字符),会使用简化格式:═══标题═══
252+
/// 输出后会自动添加一个空行以便与后续内容分隔。
253+
/// </para>
254+
/// </remarks>
255+
/// <example>
256+
/// <code>
257+
/// // 显示纯分隔线
258+
/// console.ShowLineTitle("");
259+
///
260+
/// // 显示带标题的分隔线
261+
/// console.ShowLineTitle("初始化完成");
262+
/// console.ShowLineTitle("系统配置");
263+
/// </code>
264+
/// </example>
265+
public void ShowLineTitle(string title = "")
266+
{
267+
ArgumentNullException.ThrowIfNull(title, nameof(title));
268+
var stringWidth = title.GetDisplayWidth();
269+
// 如果标题过长,使用简单的三等号包围格式
270+
if (stringWidth - 4 > _frameLength)
271+
{
272+
Console.WriteLine($"═══{title}═══");
273+
}
274+
else
275+
{
276+
// 计算剩余空间并平均分配到标题两侧
277+
var remaining = _frameLength - stringWidth;
278+
remaining /= 2;
279+
var padding = '═'.RepeatChar(remaining);
280+
// 输出居中的分隔线,标题两侧用等号字符填充
281+
Console.WriteLine((padding + title + padding));
282+
}
283+
284+
// 添加空行以便与后续内容分隔
285+
Console.WriteLine();
286+
}
287+
}

0 commit comments

Comments
 (0)