ASP.NET Core - 日誌記錄系統(一)

来源:https://www.cnblogs.com/wewant/archive/2023/07/19/17489802.html
-Advertisement-
Play Games

# 一、日誌記錄 日誌記錄是什麼?簡單而言,就是通過一些方式記錄應用程式運行中的某一時刻的狀態,保留應用程式當時的信息。這對於我們進行應用程式的分析、審計以及維護有很大的作用。 作為程式員,我們恐怕誰也不敢保證我們開發的軟體應用一定不存在BUG,一定不會出現故障,而當故障出現的時候,日誌就是我們排查 ...


一、日誌記錄

日誌記錄是什麼?簡單而言,就是通過一些方式記錄應用程式運行中的某一時刻的狀態,保留應用程式當時的信息。這對於我們進行應用程式的分析、審計以及維護有很大的作用。

作為程式員,我們恐怕誰也不敢保證我們開發的軟體應用一定不存在BUG,一定不會出現故障,而當故障出現的時候,日誌就是我們排查故障的首要依據,排查故障的第一步一定是查看故障發生時的日誌信息。

當然,日誌也不僅僅只是在排查故障的時候有用,這類稱為錯誤日誌,比較常談的還有安全日誌、審計日誌等等,它根據應用場景、企業團隊對其認知和需要有不同的應用。日誌記錄在軟體工程中更是一種思想,而不止是一種開發技術實現,它被認為是產品團隊對其產品需求沒有特別要求的非功能性使用場景,在軟體框架、開發實現中基本是一種必備的橫切功能點,現在的各種開發語言、框架中基本都具備日誌記錄的實現。

二、ASP.Net Core 的日誌記錄

.NET Core 框架中內置了日誌記錄系統,支持通過統一的 API 進入日誌的記錄,並且支持通過配置各種日誌提供程式以各種不同的方式保存日誌信息,不僅有多種內置的日誌提供程式,也相容各種按照標準規範實現的第三方框架。以下演示代碼基於 .NET 7 。

2.1. 日誌記錄系統的接入

當我們通過 VS 應用模板創建一個 ASP.NET Core 的應用時,預設將日誌記錄系統添加到應用中,內部實際上時在創建 HostApplicationBuilder 的過程中,通過 AddLogging() 註冊了日誌相關的服務,並配置了 ConsoleDebugEventSourceEventLog(僅Windows)共四種日誌記錄提供程式。

var builder = WebApplication.CreateBuilder(args);

除此之外,我們也可以引入 Microsoft.Extensions.Hosting 包,自行通過通用主機創建應用,以下代碼中也預設添加了日誌記錄系統:

var host = Host.CreateDefaultBuilder().Build();

通過查看源碼,可以看到:

image

image

image

image

這兩種方式最終都是通過 HostingHostBuilderExtensions 中的 AddDefaultServices() 方法向容器中註入日誌相關的服務,並根據不同平臺配置了不同的日誌提供程式。

除了預設的幾種提供程式,我們也可以通過以下兩種方式根據自己的實際需要,添加其他的日誌提供程式,如比較常用的文件記錄提供程式將日誌輸出到文本文件中,或者清除預設的提供程式進行自定義。在.NET 6、.NET 7 中,微軟推進用第二種方式替代第一種方式。

builder.Host.ConfigureLogging(logging =>
{
    // 清除已經註入的日誌提供程式
    logging.ClearProviders();
    logging.AddConsole();
});

// 清除已經註入的日誌提供程式
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Logging.AddJsonConsole();

關於日誌記錄提供程式,下麵再進行詳細的介紹。

2.2 記錄日誌

將日誌記錄系統集成到應用之後,使用方式就非常簡單了。.NET Core 日誌記錄系統提供統一的 API 進行日誌的記錄,無論底層使用的是什麼日誌提供程式,無論最終是將日誌記錄在哪裡。

我們可以通過依賴註入,在需要記錄日誌的類中從容器中解析出 ILogger<TCategoryName> 這樣一個日誌記錄器實例。日誌記錄器在創建的時候需要指定日誌類別,它會與該記錄器的記錄的每一條日誌關聯,方便我們在眾多的日誌信息中查找特定的日誌。ILogger<TCategoryName> 中的泛型會作為該記錄器的日誌類別,按照 .NET 體系下不成文的約定,一般情況下使用註入記錄器的類作為泛型類型,最終在日誌信息中會以該類的全類名作為日誌類別。

[ApiController]
[Route("[controller]/[action]")]
public class WeatherForecastController : ControllerBase
{
	private readonly ILogger<WeatherForecastController> _logger;

	public WeatherForecastController(ILogger<WeatherForecastController> logger)
	{
		_logger = logger;
	}
}

我們也可以顯式指定日誌類別,日誌類別實質就是一個字元串,這時我們可以註入 ILoggerFactory 實例,之後通過 ILoggerFactory.CreateLogger 方法自行創建記錄器。

[ApiController]
[Route("[controller]/[action]")]
public class WeatherForecastController : ControllerBase
{
	private ILogger _logger;

	public WeatherForecastController(ILoggerFactory loggerFactory)
	{
		_logger = loggerFactory.CreateLogger("MyLogger");
	}
 }

之後,就是使用記錄器在我們需要的位置記錄日誌信息,記錄器提供了豐富的 API 方便我們記錄各種日誌:

[ApiController]
[Route("[controller]/[action]")]
public class WeatherForecastController : ControllerBase
{
	private readonly ILogger<WeatherForecastController> _logger;

	public WeatherForecastController(ILogger<WeatherForecastController> logger)
	{
		_logger = logger;
	}

	[HttpGet]
	public Task Get()
	{
		// 各種日誌API對應各種日誌級別
		// 斷點
		_logger.LogTrace("這是一個斷點日誌");
		//調試
		_logger.LogDebug("this is a debug.");
		//信息
		_logger.LogInformation("this is an info.");
		//警告
		_logger.LogWarning("this is a warning.");
		//錯誤
		_logger.LogError("this is an error.");
		//當機
		_logger.LogCritical("this is Critical");
		// 多個重載方法,支持字元串占位符
		_logger.LogInformation("this is an info {date} {level}.", DateTime.Now, 1);
		// 支持傳入異常,記錄異常信息
		_logger.LogError(new Exception(), "this is an error.");
	   // 指定日誌ID,方便同類異常的篩選, 常用的日誌id可以定義為常量
		_logger.LogError(1001, new Exception(), "this is an error with eventId.");
 // 自行指定日誌級別
 _logger.Log(LogLevel.Information, "loging an info.");

		return Task.CompletedTask;
	}
}

當調用該介面的時候,可以看到控制臺中輸出了我們記錄的日誌內容:

image

這裡沒有輸出 TraceDebug 日誌,是因為預設配置中輸出的最低日誌級別是 Information,要使 TraceDebug 這兩類日誌可以正常輸出,需要我們進行配置。

image

image

2.3 基本配置

2.3.1 日誌級別

上面的代碼中講到,記錄日誌時我們可以指定當前日誌信息的級別,日誌級別表示日誌的嚴重程度,.NET Core 框架日誌系統中日誌級別如下,一共分為7個等級,從輕到重為(最後的None較為特殊):

日誌級別 描述
Trace 0 追蹤級別,包含最詳細的信息。這些信息可能包含敏感數據,預設情況下是禁用的,並且絕不能出現在生產環境中。
Debug 1 調試級別,用於開發人員開發和調試。信息量一般比較大,在生產環境中一定要慎用。
Information 2 信息級別,該級別平時使用較多。
Warning 3 警告級別,一些意外的事件,但這些事件並不對導致程式出錯。
Error 4 錯誤級別,一些無法處理的錯誤或異常,這些事件會導致當前操作或請求失敗,但不會導致整個應用出錯。
Critical 5 致命錯誤級別,這些錯誤會導致整個應用出錯。例如記憶體不足等。
None 6 指示不記錄任何日誌

2.3.2 全局輸出配置

我們可以在記錄日誌的時候指定日誌的級別,但是並不是我們記錄的任何一個級別的日誌都會輸出保存,還得配合日誌記錄系統的配置,就像上面的例子中,最開始 DebugTrace 級別的日誌是不輸出的。

日誌記錄配置通常通過配置文件進行設置,在 appsettings.json 文件有關於日誌配置的相關節點 Logging,在我們通過 ASP.NET Core 應用模板創建項目時,就會自動生成:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  }
}

在配置文件中,我們可以通過 LogLevel 節點對日誌記錄系統全局輸出的日誌最低級別進行設置,日誌記錄系統最終會輸出大於等於我們設置的級別的日誌信息,而其他信息則不會輸出。

可以看到,在 LogLevel 節點下還有一些欄位,通過這些欄位我們還可以進行更具體的配置。其中“Default”欄位顧名思義就是預設配置,如上面配置中設置了日誌系統預設輸出的最低級別日誌為 Information,沒有進行過特殊配置的日誌記錄器全部按照這一個配置進行輸出。

我們還可以針對某一特定的日誌記錄器進行專門的設置,通過日誌記錄器創建時傳入的名稱進行篩選,支持模糊匹配(字元串 StartWith 判斷),如上面配置中的 “Microsoft.AspNetCore”,這個欄位一看就是命名空間書寫方式,也就是說全類名以該欄位開始的日誌記錄器記錄的日誌按照這個配置設置的最低日誌記錄進行記錄。如果還有更加具體的配置,如“Microsoft.AspNetCore.Mvc”,一個日誌記錄器名稱同時匹配 “Microsoft.AspNetCore”“Microsoft.AspNetCore.Mvc”,則以 “Microsoft.AspNetCore.Mvc” 的配置為準,因為 “Microsoft.AspNetCore.Mvc” 更具體。

這也是為什麼約定使用 ILogger<TCategoryName> 介面註入日誌記錄器的原因,這種方式下我們可以通過有規律的命名空間快速設置篩選最終需要輸出保存的日誌信息。當然,如果自定義的日誌記錄器名稱字元串比較有規律,那也沒有問題。

2.3.3 針對特定日誌提供程式的配置

在日常的應用開發中,往往我們都會使用不止一種方式記錄日誌,通常會同時集成多個日誌記錄提供程式,LogLevel 節點是針對所有日誌記錄提供程式的統一配置,它適用於所有沒有進行單獨配置的日誌記錄提供程式(Windows EventLog 除外。EventLog 必須顯式地進行配置,否則會使用其預設的 LogLevel.Warning)。當然,我們也可以針對不同的日誌提供程式進行單獨的配置。如:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    },
    "Console": {
      "LogLevel": {
        "Default": "Error"
       }
    },
    "Debug": {
      "LogLevel": {
        "Microsoft": "None"
       }
    },
    "EventSource": {
      "LogLevel": {
        "Default": "Trace",
        "Microsoft": "Trace",
        "Microsoft.Hosting.Lifetime": "Trace"
      }
    }
  }
}

就像 appsettings.{Environment}.jsonappsettings.json 之間的關係一樣,Logging.{Provider}.LogLevel 中的配置將會覆蓋 Logging.LogLevel 中的配置。例如 Logging.Console.LogLevel.Default 將會覆蓋 Logging.LogLevel.DefaultConsole 日誌記錄器將預設記錄 Error 及其以上級別的日誌。

剛纔提到了,Windows EventLog 比較特殊,它不會繼承 Logging.LogLevel 的配置。EventLog 預設日誌級別為 LogLevel.Warning,如果想要修改,則必須顯式進行指定。

以上講到的日誌配置方式,都是通過 appsettings.json 設置的,實際上 .NET Core 框架下配置來源不僅僅是 appsettings.json 文件,只不過它是最常用的,這一塊的內容在之前的配置系統的文章中已經詳細講過了。我們也可以通過其他的配置來源進行日誌相關的配置,例如命令行、環境變數等。

2.3.6 顯式設置

除了通過配置進行日誌記錄系統的設置之外,我們還可以在代碼中通過 AddFilter 方法顯式地設置日誌系統的相關行為配置,該方法有多個重載,如:

var builder = WebApplication.CreateBuilder(args);

// 相當於 Logging:LogLevel:Default:Information
builder.Logging.AddFilter(logging => logging >= LogLevel.Information);
// 相當於 Logging:LogLevel:Microsoft:Warning
builder.Logging.AddFilter("Microsoft", LogLevel.Warning);
// 相當於 Logging:Console:LogLevel:Microsoft:Information
builder.Logging.AddFilter<ConsoleLoggerProvider>("Microsoft", LogLevel.Information);
// 也可以更加靈活地通過篩選器設置日誌記錄規則
builder.Logging.AddFilter((provider, category, logLevel) =>
{
    if (provider.Contains("ConsoleLoggerProvider")
        && category.Contains("Controller")
        && logLevel >= LogLevel.Information)
    {
        return true;
    }
    else if (provider.Contains("ConsoleLoggerProvider")
        && category.Contains("Microsoft")
        && logLevel >= LogLevel.Information)
    {
        return true;
    }
    else
    {
        return false;
    }
});
// 設置全局的日誌輸出最小級別,日誌記錄系統預設最低級別是 Information
builder.Logging.SetMinimumLevel(LogLevel.Debug);

這種方式相對於配置會比較固化,不利於動態調整,一般來說,日誌記錄的相關配置還是配置文件中設置,所以這裡就簡單地講一下,大家知道有這種方式就行了。

2.3.4 配置篩選原理

當創建 ILogger<TCategoryName> 的對象實例時,ILoggerFactory 根據不同的日誌記錄提供程式,將會:

  1. 查找匹配該日誌記錄提供程式的配置。如果找不到,則使用通用配置。
  2. 然後匹配擁有最長首碼的配置類別。如果找不到,則使用Default配置。
  3. 如果匹配到了多條配置,則採用最後一條。
  4. 如果沒有匹配到任何配置,則使用 MinimumLevel,這是個配置項,預設是LogLevel.Information。

如上面講到的,我們可以使用 SetMinimumLevel 方法設置 MinimumLevel

對於到的 .NET Core 中的源碼是這一段:
image

在創建 ILoggerFactory 實例、創建 ILogger 實例和配置刷新的時候,都會對每一個提供程式的配置規則根據優先順序進行篩選,只有最小級別不為 None,才會創建最終的日誌記錄書寫器,否則甚至不會有書寫器。

而在具體的規則過濾邏輯中,可以看到微軟的註釋:

日誌規則過濾 LoggerRuleSelector
internal static class LoggerRuleSelector
{
	public static void Select(LoggerFilterOptions options, Type providerType, string category, out LogLevel? minLevel, out Func<string?, string?, LogLevel, bool>? filter)
	{
		filter = null;
		minLevel = options.MinLevel;

		// Filter rule selection:
		// 1. Select rules for current logger type, if there is none, select ones without logger type specified
		// 2. Select rules with longest matching categories
		// 3. If there nothing matched by category take all rules without category
		// 3. If there is only one rule use it's level and filter
		// 4. If there are multiple rules use last
		// 5. If there are no applicable rules use global minimal level

		string? providerAlias = ProviderAliasUtilities.GetAlias(providerType);
		LoggerFilterRule? current = null;
		foreach (LoggerFilterRule rule in options.RulesInternal)
		{
			if (IsBetter(rule, current, providerType.FullName, category)
				|| (!string.IsNullOrEmpty(providerAlias) && IsBetter(rule, current, providerAlias, category)))
			{
				current = rule;
			}
		}

		if (current != null)
		{
			filter = current.Filter;
			minLevel = current.LogLevel;
		}
	}

	private static bool IsBetter(LoggerFilterRule rule, LoggerFilterRule? current, string? logger, string category)
	{
		// Skip rules with inapplicable type or category
		// 別名或者全類名與當前日誌提供程式對不上的則跳過
		if (rule.ProviderName != null && rule.ProviderName != logger)
		{
			return false;
		}
		// 對日誌類別進行判斷,這裡會同時判斷通用的配置和針對特定日誌提供程式的配置
		// 也就是說某個類別,如果通用的LogLevel中配置了,如果特定的日誌特工程式中沒有重新配置覆蓋,則會使用通用配置
		// 支持通配符 * ,但 * 只能有一個
		string? categoryName = rule.CategoryName;
		if (categoryName != null)
		{
			const char WildcardChar = '*';

			int wildcardIndex = categoryName.IndexOf(WildcardChar);
			if (wildcardIndex != -1 &&
				categoryName.IndexOf(WildcardChar, wildcardIndex + 1) != -1)
			{
				throw new InvalidOperationException(SR.MoreThanOneWildcard);
			}

			ReadOnlySpan<char> prefix, suffix;
			if (wildcardIndex == -1)
			{
				prefix = categoryName.AsSpan();
				suffix = default;
			}
			else
			{
				prefix = categoryName.AsSpan(0, wildcardIndex);
				suffix = categoryName.AsSpan(wildcardIndex + 1);
			}

			if (!category.AsSpan().StartsWith(prefix, StringComparison.OrdinalIgnoreCase) ||
				!category.AsSpan().EndsWith(suffix, StringComparison.OrdinalIgnoreCase))
			{
				return false;
			}
		}
		// 如果相同的類別,則以特定提供程式的配置優先
		if (current?.ProviderName != null)
		{
			if (rule.ProviderName == null)
			{
				return false;
			}
		}
		else
		{
			// We want to skip category check when going from no provider to having provider
			if (rule.ProviderName != null)
			{
				return true;
			}
		}
		// 特定的類別優先於預設的 Default 類別
		if (current?.CategoryName != null)
		{
			if (rule.CategoryName == null)
			{
				return false;
			}
			// 類別名稱更詳細的優先
			if (current.CategoryName.Length > rule.CategoryName.Length)
			{
				return false;
			}
		}

		return true;
	}
}

LoggerFilterOptions 中的規則是怎麼來的呢?在通過主機構建應用的時候會通過配置文件載入相關的配置,並將配置轉化為規則

image

image

最終,配置文件中的每一個日誌類別的配置都會結合日誌提供程式轉化為一項規則,預設的 LogLevel 中的配置轉換成的規則中 ProviderNamenull,預設的 Default 類別對於的規則 CategoryNamenull

日誌規則配置 LoggerFilterConfigureOptions
internal sealed class LoggerFilterConfigureOptions : IConfigureOptions<LoggerFilterOptions>
{
	private const string LogLevelKey = "LogLevel";
	private const string DefaultCategory = "Default";
	private readonly IConfiguration _configuration;

	public LoggerFilterConfigureOptions(IConfiguration configuration)
	{
		_configuration = configuration;
	}

	public void Configure(LoggerFilterOptions options)
	{
		LoadDefaultConfigValues(options);
	}

	private void LoadDefaultConfigValues(LoggerFilterOptions options)
	{
		if (_configuration == null)
		{
			return;
		}

		options.CaptureScopes = GetCaptureScopesValue(options);

		foreach (IConfigurationSection configurationSection in _configuration.GetChildren())
		{
			if (configurationSection.Key.Equals(LogLevelKey, StringComparison.OrdinalIgnoreCase))
			{
				// Load global category defaults
				LoadRules(options, configurationSection, null);
			}
			else
			{
				IConfigurationSection logLevelSection = configurationSection.GetSection(LogLevelKey);
				if (logLevelSection != null)
				{
					// Load logger specific rules
					string logger = configurationSection.Key;
					LoadRules(options, logLevelSection, logger);
				}
			}
		}

		[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
			Justification = "IConfiguration.GetValue is safe when T is a bool.")]
		bool GetCaptureScopesValue(LoggerFilterOptions options) => _configuration.GetValue(nameof(options.CaptureScopes), options.CaptureScopes);
	}

	private static void LoadRules(LoggerFilterOptions options, IConfigurationSection configurationSection, string? logger)
	{
		foreach (System.Collections.Generic.KeyValuePair<string, string?> section in configurationSection.AsEnumerable(true))
		{
			if (TryGetSwitch(section.Value, out LogLevel level))
			{
				string? category = section.Key;
				if (category.Equals(DefaultCategory, StringComparison.OrdinalIgnoreCase))
				{
					category = null;
				}
				var newRule = new LoggerFilterRule(logger, category, level, null);
				options.Rules.Add(newRule);
			}
		}
	}

	private static bool TryGetSwitch(string? value, out LogLevel level)
	{
		if (string.IsNullOrEmpty(value))
		{
			level = LogLevel.None;
			return false;
		}
		else if (Enum.TryParse(value, true, out level))
		{
			return true;
		}
		else
		{
			throw new InvalidOperationException(SR.Format(SR.ValueNotSupported, value));
		}
	}
}

而這些是對配置中的規則的處理,最終得到的是 miniLevel,每次寫日誌的時候會先將當前日誌信息的級別和配置的最低級別進行比較,如果我們還有在代碼中通過 AddFilter 擴展方法增加的額外的規則的化,會在配置規則過濾完成之後再過濾(也就是說,Filter 中是不會有低於配置的級別的日誌的),如果都不通過,則不會轉到最終的記錄器。

image

image

2.3.5 日誌作用域

有些時候,我們可能希望某一些日誌集中在一起顯示,或者在進行一些強關聯的邏輯操作時,希望記錄的日誌中保留有關聯信息,這時候就可以使用日誌作用域。日誌作用域依賴於特定的日誌記錄提供程式的支持,並不是所有的提供程式都支持,內置的提供程式中 Console、AzureAppServicesFile 和 AzureAppServicesBlob 提供了相應的支持。可以通過以下的方式啟用日誌作用域:

(1) 通過日誌記錄器的 BeginScope 創建作用域,並使用 using 塊包裝。

[ApiController]
[Route("[controller]/[action]")]
public class WeatherForecastController : ControllerBase
{
	private readonly ILogger<WeatherForecastController> _logger;

	public WeatherForecastController(ILogger<WeatherForecastController> logger)
	{
		_logger = logger;
	}

	[HttpGet]
	public Task Get()
	{
		// 創建一個日誌域,以下日誌會被當作一個整體
		using (_logger.BeginScope("this is a log scope"))
		{
			// 除了使用特定級別的API,也可以使用Log方法,動態指定級別
			_logger.Log(LogLevel.Information, "logging a scope info.");
			_logger.Log(LogLevel.Warning, "logging a scope warning.");
		}

		return Task.CompletedTask;
	}
}

(2) 在配置中針對日誌提供程式添加 "IncludeScopes: true" 配置

{
  "Logging": {
    "LogLevel": {
      "Default": "Trace",
      "Microsoft.AspNetCore": "Warning"
    },
    "Console": {
      "IncludeScopes": true,
      "LogLevel": {
        "Default": "Information",
        "Microsoft.AspNetCore": "Warning"
      }
    }
  }
}

image

從最終的輸出中可以看到,同一個作用域中記錄的日誌都帶上了創建作用域時設置的標記。同時也可以看到,記錄的日誌中多了 SpanId、TraceId、ParentId 這些內容,這是日誌記錄系統隱式創建範圍對象,這些信息源自於每一次的Http 請求,方便對一次 Http 請求中各個步驟的跟蹤。對於這些信息的配置,可以通過 ActivityTrackingOptions 設置。

var builder = WebApplication.CreateBuilder(args);
builder.Logging.Configure(option =>
{
    option.ActivityTrackingOptions = ActivityTrackingOptions.SpanId | ActivityTrackingOptions.TraceId;
});

image

以下是一些註意點:

  • 若要在 Startup.Configure 方法中記錄日誌,直接在參數上註入 ILogger<Startup> 即可。
  • 不支持在 Startup.ConfigureServices 方法中使用 ILogger,因為此時 DI 容器還未配置完成。
  • 沒有非同步的日誌記錄方法。日誌記錄動作執行應該很快,不值的犧牲性能使用非同步方法。如果日誌記錄動作比較耗時,如記錄到 MSSQL 中,那麼請不要直接寫入 MSSQL。你應該考慮先將日誌寫入到快速存儲介質,如記憶體隊列,然後通過後臺工作線程將其從記憶體轉儲到 MSSQL 中。
  • 無法使用日誌記錄 API 在應用運行時更改日誌記錄配置。不過,一些配置提供程式(如文件配置提供程式)可重新載入配置,這可以立即更新日誌記錄配置。


參考文章:

.NET Core 和 ASP.NET Core 中的日誌記錄 | Microsoft Learn
理解ASP.NET Core - 日誌(Logging) - xiaoxiaotank - 博客園 (cnblogs.com)



ASP.NET Core 系列:

目錄:ASP.NET Core 系列總結
上一篇:ASP.NET Core - 緩存之分散式緩存
下一篇:[ASP.NET Core - 日誌記錄系統(二)]


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • # **TCP編程** - **基本介紹** 1. 基於客戶端——服務端的網路通信; 2. 底層使用的是TCP/IP協議; 3. 應用場景舉例:客戶端發送數據,服務端接收並顯示在控制台; 4. 基於Socket的TCP編程; - **示意圖** ![](https://img2023.cnblogs ...
  • 下麵這段優秀的代碼節選自hutool-DateUtil(hutool-all-4.5.18.jar ,maven坐標:cn.hutool:hutool-all:4.5.18),香香的,甜甜的! ...
  • 博客推行版本更新,成果積累制度,已經寫過的博客還會再次更新,不斷地琢磨,高質量高數量都是要追求的,工匠精神是學習必不可少的精神。因此,大家有何建議歡迎在評論區踴躍發言,你們的支持是我最大的動力,你們敢投,我就敢肝 ...
  • **本文詳細解析了Python的logging模塊,從基本介紹到實際應用和最佳實踐。我們通過具體的代碼示例解釋瞭如何高效地使用這個模塊進行日誌記錄,以及如何避免常見的陷阱,旨在幫助讀者更好地掌握這個強大的工具。** ![file](https://img2023.cnblogs.com/other/ ...
  • # Spring6 初始 @[toc] ## 每博一文案: ```tex 人生的態度是:抱有最大的希望。 盡最大的努力,做最壞的打算。 —————— 柏拉圖《理想國》 ``` ## 1. 初始 Spring6 閱讀以下代碼: ```java package com.powernode.oa.cont ...
  • 這篇文章主要記錄了本次遇到的問題:即maven在面對不同版本的jar包在pom文件中同時聲明會存在載入覆蓋的問題,於是通過查詢網上相關資料對maven包的載入規則介紹,並通過實際場景對其進行分析驗證 ...
  • # 1.模塊 ``` import 模塊名 ``` ## 1.1 模塊就是程式 任何python程式都可以作為模塊導入,並標明程式(模塊)的位置 ``` import sys sys.path.append('路徑') ``` ``` import hello // 在同一文件夾下 ``` 會在該文 ...
  • [toc] # 引入 - Microsoft.EntityFrameworkCore - Microsoft.EntityFrameworkCore.Design - Microsoft.EntityFrameworkCore.SqlServer - Microsoft.EntityFramewor ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...