<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Serilog 最佳實(shí)踐

          共 14949字,需瀏覽 30分鐘

           ·

          2021-08-11 12:17


          Serilog 最佳實(shí)踐

          概述

          Serilog[1]是 Microsoft .NET 的結(jié)構(gòu)化日志記錄庫,并已成為Checkout.com 上NET 的首選日志記錄庫。它支持各種日志記錄目的地(稱為接收器[2])包從標(biāo)準(zhǔn)控制臺和基于文件的接收器到日志服務(wù),如 Datadog。

          本指南最初是我們工程手冊中的一篇文章,在收到內(nèi)部積極反饋后,我決定在我的博客上發(fā)布它。

          內(nèi)容

          1.標(biāo)準(zhǔn)日志屬性2.日志記錄基礎(chǔ)知識1.記錄一切2.選擇合適的日志記錄級別3.定時操作源4.上下文5.HTTP 日志記錄6.日志的職責(zé)3.日志內(nèi)容的采集1.Serilog標(biāo)準(zhǔn)采集方法2.通過全局屬性采集日志4.關(guān)聯(lián)日志5.消息模板1.消息模板推薦6.日志和診斷上下文1.日志上下文2.診斷上下文7.配置8.生產(chǎn)日志1.當(dāng)日志變得不僅僅是日志9.其他工具和實(shí)用程序1.在本地使用 Seq2.按日志類型記錄日志3.按屬性塊記錄日志4.按請求記錄日志

          標(biāo)準(zhǔn)日志屬性

          標(biāo)準(zhǔn)化日志事件屬性使您能夠充分利用日志搜索和分析工具。在適用的情況下使用以下屬性:

          ApplicationName生成日志事件的應(yīng)用程序的名稱
          ClientIP發(fā)出請求的客戶端的 IP 地址
          CorrelationId可用于跨多個應(yīng)用程序邊界跟蹤請求的 ID
          Elapsed操作完成所用的時間(以毫秒為單位)
          EventType用于確定消息類型的消息模板的哈希值
          MachineName運(yùn)行應(yīng)用程序的機(jī)器的名稱
          Outcome手術(shù)的結(jié)果
          RequestMethodHTTP 請求方法,例如 POST
          RequestPathHTTP 請求路徑
          SourceContext日志源自的組件/類的名稱
          StatusCodeHTTP 響應(yīng)狀態(tài)碼
          UserAgentHTTP 用戶代理
          Version正在運(yùn)行的應(yīng)用程序的版本

          上面的很多屬性都來自于 Serilog 自己的擴(kuò)展,例如Serilog Timings[3](用于計(jì)時操作)和Serilog 請求日志記錄[4]

          日志記錄基礎(chǔ)知識

          記錄一切

          通常,記錄所有可以深入了解您的應(yīng)用程序和用戶行為的內(nèi)容,例如:

          ?代碼中的主要分支點(diǎn)?遇到錯誤或意外值時?任何 IO 或資源密集型操作?重大領(lǐng)域事件?請求失敗和重試?耗時的批處理操作的開始和結(jié)束

          選擇合適的日志記錄級別

          對您的日志記錄要慷慨,但對您的日志記錄級別要嚴(yán)格。在幾乎所有情況下,您的日志級別都應(yīng)該是Debug. 使用Information的日志事件,將在生產(chǎn)中需要確定運(yùn)行狀態(tài)或應(yīng)用程序的正確性,WarningError突發(fā)事件,如異常。

          請注意,該Error級別應(yīng)保留用于您打算對其采取行動的事件。如果某些事情成為正常的應(yīng)用程序行為(例如,請求輸入驗(yàn)證失敗),您應(yīng)該降級日志級別以減少日志“噪音”。

          定時操作

          將應(yīng)用程序中的每個資源密集型操作(例如 IO)與指標(biāo)代碼一起記錄下來。這在本地運(yùn)行應(yīng)用程序以查看應(yīng)用程序瓶頸或響應(yīng)時間消耗的情況時非常有用。該Serilog時序庫[5]提供了一個方便的方式來做到這一點(diǎn):

          using (_logger.TimeDebug("Sending notification to Slack channel {Channel} with {WebhookUrl}", _slackOptions.Channel, _slackOptions.WebhookUrl))using (_metrics.TimeIO("http", "slack", "send_message")){
          }

          源上下文

          SourceContext屬性用于跟蹤日志事件的來源,通常是使用記錄器的 C# 類。ILogger使用依賴注入將 Serilog 注入到類中是很常見的。為確保SourceContext正確設(shè)置,請使用ForContext擴(kuò)展名:

          public TheThing(ILogger logger){    _logger = logger?.ForContext<TheThing>() ?? throw new ArgumentNullException(nameof(_logger));}

          HTTP 日志記錄

          使用Serilog 請求日志記錄中間件[6]來記錄 HTTP 請求。這會自動包含上面列出的許多 HTTP 屬性并生成以下日志消息:

          HTTP POST /payments responded 201 in 1348.6188 ms

          將以下內(nèi)容添加到您的應(yīng)用程序啟動中以添加中間件:

          public void Configure(IApplicationBuilder app){    app.UseHealthAndMetricsMiddleware();    app.UseSerilogRequestLogging();    app.UseAuthentication();    app.UseMvc();}

          請注意,在health 和 metrics 中間件之后添加了 Serilog中間件。這是為了避免每次 AWS 負(fù)載均衡器命中您的健康檢查端點(diǎn)時生成日志。


          記錄 HTTP 資源

          Serilog 中間件默認(rèn)記錄請求路徑。如果您確實(shí)需要查看對應(yīng)用程序中特定端點(diǎn)的所有請求,如果路徑包含標(biāo)識符等動態(tài)參數(shù),您可能會遇到挑戰(zhàn)。

          為了解決這個問題,記錄資源名稱,在我們的應(yīng)用程序中,按照慣例,它是Name賦予相應(yīng)路由的屬性。這是這樣檢索的:

          public static string GetMetricsCurrentResourceName(this HttpContext httpContext){    if (httpContext == null)        throw new ArgumentNullException(nameof(httpContext));
          Endpoint endpoint = httpContext.Features.Get<IEndpointFeature>()?.Endpoint;
          #if NETCOREAPP3_1 return endpoint?.Metadata.GetMetadata<EndpointNameMetadata>()?.EndpointName;#else return endpoint?.Metadata.GetMetadata<IRouteValuesAddressMetadata>()?.RouteName;#endif}

          日志的職責(zé)

          過度全面的日志記錄不僅會對您的應(yīng)用程序產(chǎn)生性能影響,而且還會使診斷問題變得更加困難,并增加暴露敏感信息的風(fēng)險。

          Serilog 支持結(jié)構(gòu)化對象輸出,允許將復(fù)雜對象作為日志中的參數(shù)傳遞。這應(yīng)該謹(jǐn)慎使用,如果您的主要目標(biāo)是對相關(guān)屬性進(jìn)行分組,您最好初始化一個新的匿名對象,這樣您就可以明確哪些信息被推送到日志中。

          傾向于使用 Serilog 的診斷上下文功能(下面討論)將日志折疊為單個日志條目。

          收集日志

          將附加信息推送到您的日志中有助于提供有關(guān)特定事件的附加上下文。

          標(biāo)準(zhǔn) Serilog 收集器

          您可以使用收集器來豐富應(yīng)用程序生成的所有日志事件。我們建議使用以下 Serilog 濃縮器:

          ?日志上下文收集器 - 內(nèi)置于 Serilog,此豐富器可確保添加到日志上下文的[7]任何屬性都被推送到日志事件中?環(huán)境收集器[8]- 使用機(jī)器或當(dāng)前用戶名采集日志

          可以使用Enrich.WithSerilog的fluent APILoggerConfiguration或通過您的appsettings.json文件(推薦)指定增強(qiáng)器:

          {  "Serilog": {    "Using": [      "Serilog.Sinks.Console"    ],    "MinimumLevel": {      "Default": "Information"    },    "WriteTo": [      {        "Name": "Console"      }    ],    "Enrich": [      "FromLogContext",      "WithMachineName"    ],    "Properties": {      "ApplicationName": "Gateway API"    }  }}

          全局屬性采集

          您還可以全局指定屬性。上面的片段appsettings.json演示了我們通常如何設(shè)置ApplicationName屬性。在某些情況下,我們需要在啟動時計(jì)算屬性,這可以使用 Fluent API 來完成:

          loggerConfiguration.ReadFrom.Configuration(hostContext.Configuration)    .EnrichWithEventType()    .Enrich.WithProperty("Version", ReflectionUtils.GetAssemblyVersion<Program>());

          關(guān)聯(lián)日志

          為了關(guān)聯(lián)屬于同一請求的日志,甚至跨多個應(yīng)用程序,請CorrelationId向日志添加一個屬性。

          在 HTTP 應(yīng)用程序中,我們通常從HttpContext.TraceIdentifier屬性映射它。這是使用Cko-Correlation-Id標(biāo)頭在內(nèi)部 API 之間傳遞的。我們使用以下擴(kuò)展來獲取 _current_correlation ID:

          public static string GetCorrelationId(this HttpContext httpContext){    httpContext.Request.Headers.TryGetValue("Cko-Correlation-Id", out StringValues correlationId);    return correlationId.FirstOrDefault() ?? httpContext.TraceIdentifier;}

          請注意,如果應(yīng)用程序面向用戶,則不應(yīng)依賴提供的相關(guān) ID 標(biāo)頭。

          為了確保將關(guān)聯(lián) ID 推送到每個日志事件中,我們使用以下使用 Serilog 的中間件LogContext(本文稍后將詳細(xì)討論):

          public class RequestLogContextMiddleware{    private readonly RequestDelegate _next;
          public RequestLogContextMiddleware(RequestDelegate next) { _next = next; }
          public Task Invoke(HttpContext context) { using (LogContext.PushProperty("CorrelationId", context.GetCorrelationId())) { return _next.Invoke(context); } }}

          消息模板

          日志消息應(yīng)提供事件的簡短描述。我們通常看到開發(fā)人員創(chuàng)建過于冗長的消息作為在事件中包含額外數(shù)據(jù)的手段,例如:

          _logger.Information("Storing payment state in Couchbase for Payment ID {PaymentId} and current state {State}", paymentId, state);

          相反,您可以使用ForContext(或本文底部的屬性包豐富器)仍然包含數(shù)據(jù)但具有更簡潔的消息:

          _logger    .ForContext("PaymentId", paymentId)    .ForContext("State", state)    .Information("Storing payment state in Couchbase");

          消息模板推薦

          Fluent風(fēng)格指南

          好的 Serilog 事件使用屬性名稱作為消息中的內(nèi)容來提高可讀性并使事件更緊湊,例如:

          _logger.Information("Processed {@Position} in {Elapsed:000} ms.", position, elapsedMs);

          句子與片段

          日志事件消息是片段,而不是句子;為了與使用 Serilog 的其他庫保持一致,請盡可能避免尾隨句點(diǎn)/句號。

          模板與消息

          Serilog 事件具有關(guān)聯(lián)的消息模板,而不是消息。在內(nèi)部,Serilog 解析和緩存每個模板(最多固定大小限制)。將日志方法的字符串參數(shù)視為消息,如下例所示,會降低性能并消耗緩存內(nèi)存。例如,避免:

          Log.Information("The time is " + DateTime.Now);

          而是使用消息屬性:

          Log.Information("The time is {Now}", DateTime.Now);

          除了在日志消息中使用字符串連接/插值的性能開銷之外,它還意味著無法計(jì)算一致的事件類型(請參閱事件類型豐富器),從而無法找到特定類型的所有日志。

          日志和診斷上下文

          Serilog 支持兩種可用于增強(qiáng)日志的上下文感知功能。

          日志上下文

          LogContext可用于動態(tài)地添加和移除來自周圍“執(zhí)行上下文”性能; 例如,在事務(wù)期間寫入的所有消息都可能帶有該事務(wù)的 id,等等。

          RequestLogContextMiddleware上面的介紹演示了如何推動CorrelationId請求到LogContext在請求的開始。這可確保該請求中的所有日志都包含該屬性。


          更多信息可以在Serilog wiki[9]上找到。

          診斷上下文

          日志記錄的一個挑戰(zhàn)是上下文并不總是預(yù)先知道。例如,在處理 HTTP 請求的過程中,隨著我們通過 HTTP 管道(例如了解用戶的身份)

          獲得

          額外的上下文。雖然LogContext我們所有人都會在附加信息可用時創(chuàng)建新上下文,但此信息僅在 _subsequent_log 條目中可用。這通常會導(dǎo)致日志數(shù)量增加,只是為了捕獲有關(guān)整個請求或操作的所有信息。


          診斷上下文提供了一個執(zhí)行上下文(類似于LogContext),其優(yōu)點(diǎn)是可以在其整個生命周期中進(jìn)行豐富。請求日志中間件然后使用它來豐富最終的“日志完成事件”。這允許我們將許多不同的日志操作折疊為一個日志條目,其中包含來自請求管道中許多點(diǎn)的信息,例如:


          SEQ 中的豐富屬性


          在這里您可以看到,不僅有中間件發(fā)出的 HTTP 屬性,還有應(yīng)用程序數(shù)據(jù),例如AcquirerIdMerchantNameResponseCode。這些數(shù)據(jù)點(diǎn)來自請求中的不同點(diǎn),但通過IDiagnosticContext接口推送到診斷上下文中:

          public class HomeController : Controller{    readonly IDiagnosticContext _diagnosticContext;
          public HomeController(IDiagnosticContext diagnosticContext) { _diagnosticContext = diagnosticContext ?? throw new ArgumentNullException(nameof(diagnosticContext)); }
          public IActionResult Index() { // The request completion event will carry this property _diagnosticContext.Set("CatalogLoadTime", 1423);
          return View(); }

          在非 HTTP 應(yīng)用程序中使用診斷上下文

          診斷上下文不限于在 ASP.NET Core 中使用。它也可以以與請求日志中間件非常相似的方式在非 HTTP 應(yīng)用程序中使用。例如,我們使用它在 SQS 使用者中生成完成日志事件。

          配置

          Serilog 可以使用 Fluent API 或通過 Microsoft 配置系統(tǒng)進(jìn)行配置。我們建議使用配置系統(tǒng),因?yàn)榭梢栽诓话l(fā)布應(yīng)用程序新版本的情況下更改日志配置。

          為此,添加Serilog.Settings.Configuration[10]包并按如下方式配置 Serilog:

          public static IHostBuilder CreateHostBuilder(string[] args) =>    Host.CreateDefaultBuilder(args)        .UseSerilog((hostContext, loggerConfiguration) =>        {            loggerConfiguration.ReadFrom.Configuration(hostContext.Configuration);        })        .ConfigureAppConfiguration((hostingContext, config) =>        {            config                .AddEnvironmentVariables(prefix: "FLOW_")                .AddAwsSecrets();        })        .ConfigureWebHostDefaults(webBuilder =>        {            webBuilder.UseStartup<Startup>();          });

          您現(xiàn)在可以通過任何支持的配置提供程序配置 Serilog。通常我們appsettings.json用于全局設(shè)置并通過生產(chǎn)中的環(huán)境變量配置實(shí)際接收器(因?yàn)槲覀儾幌朐诒镜剡\(yùn)行時使用我們的遠(yuǎn)程日志服務(wù)):

          {  "Serilog": {    "Using": [      "Serilog.Sinks.Console",      "Serilog.Sinks.Datadog.Logs"    ],    "MinimumLevel": {      "Default": "Information"    },    "WriteTo": [      {        "Name": "Console"      }    ],    "Enrich": [      "FromLogContext",      "WithMachineName"    ],    "Properties": {      "ApplicationName": "Flow API"    }  }}

          生產(chǎn)環(huán)境下使用日志

          在生產(chǎn)中部署應(yīng)用程序時,請確保相應(yīng)地配置日志記錄:

          ?控制臺日志記錄應(yīng)限于Error. 在 .NET 中,寫入控制臺是一個阻塞調(diào)用,會對性能產(chǎn)生重大影響[11]?應(yīng)為Information及以上配置全局日志記錄。

          據(jù)了解,在新項(xiàng)目發(fā)布期間,您可能需要更多信息來建立對解決方案的信心或診斷任何預(yù)期的初期問題。與其Information為此升級您的日志條目,不如考慮Debug在有限的時間內(nèi)啟用級別。

          從開發(fā)人員那里聽到的一個常見問題是他們?nèi)绾卧谶\(yùn)行時動態(tài)切換日志級別。雖然這是可能的,但也可以使用藍(lán)/綠部署來實(shí)現(xiàn)。使用降低的日志級別配置和部署非活動環(huán)境,然后通過加權(quán)目標(biāo)組切換部分或全部流量。

          當(dāng)日志變得不僅僅是日志

          日志可以提供對應(yīng)用程序的大量洞察,并且在許多情況下足以處理日常支持請求或故障排除。然而,在某些情況下,日志可能不能確保你的工作正確,有許多警告信號:


          ?您發(fā)現(xiàn)自己向非技術(shù)用戶開放應(yīng)用程序日志?日志用于生成應(yīng)用程序指標(biāo)?更多信息被“塞進(jìn)”日志以滿足常見的支持請求或報告要求

          在這些情況下,您可能需要為您的產(chǎn)品考慮專用工具。許多團(tuán)隊(duì)開發(fā)了類似“Inspector”的應(yīng)用程序,將關(guān)鍵系統(tǒng)和業(yè)務(wù)數(shù)據(jù)聚合在一起,以處理可以提供給非技術(shù)利益相關(guān)者的 BAU 請求。此外,您可能會發(fā)現(xiàn)需要將應(yīng)用程序中的數(shù)據(jù)推送到報告和分析工具中。

          日志的有效性取決于您記錄的內(nèi)容和記錄的內(nèi)容。


          其他工具和實(shí)用程序

          在本地使用 Seq

          Seq[12]是由 Serilog 的作者創(chuàng)建的免費(fèi)(供本地使用)日志記錄工具。它提供高級搜索和過濾功能以及對結(jié)構(gòu)化日志數(shù)據(jù)的完全訪問。雖然我們的日志記錄要求現(xiàn)在超出了 Seq 所能提供的范圍,但它仍然是本地測試的一個很好的選擇。

          我們通常在 docker 中啟動 Seq 作為單獨(dú)的 docker-compose 文件 ( docker-compose-logging.hml) 的一部分:

          version: "3.5"
          services:
          seq: image: datalust/seq:latest container_name: seq ports: - '5341:80' environment: - ACCEPT_EULA=Y networks: - gateway-network
          networks:gateway-network: name: gateway-network

          并配置我們的appsettings.Development.json文件以使用 Seq 接收器:

          {  "Serilog": {    "Using": [      "Serilog.Sinks.Console",      "Serilog.Sinks.Seq"    ],    "MinimumLevel": {      "Default": "Debug",      "Override": {        "Microsoft": "Warning"      }    },    "WriteTo": [      {        "Name": "Console"      },      {        "Name": "Seq",        "Args": {          "serverUrl": "http://localhost:5341",          "apiKey": "none"        }      }    ]  }}

          事件類型收集器

          通常我們需要唯一標(biāo)識相同類型的日志。一些接收器(例如Seq)[13]通過散列消息模板來自動執(zhí)行此操作。為了在其他接收器中復(fù)制相同的行為,我們創(chuàng)建了以下使用Murmerhash 算法[14]收集器[15]

          internal class EventTypeEnricher : ILogEventEnricher{    public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)    {        if (logEvent is null)            throw new ArgumentNullException(nameof(logEvent));
          if (propertyFactory is null) throw new ArgumentNullException(nameof(propertyFactory));
          Murmur32 murmur = MurmurHash.Create32(); byte[] bytes = Encoding.UTF8.GetBytes(logEvent.MessageTemplate.Text); byte[] hash = murmur.ComputeHash(bytes); string hexadecimalHash = BitConverter.ToString(hash).Replace("-", ""); LogEventProperty eventId = propertyFactory.CreateProperty("EventType", hexadecimalHash); logEvent.AddPropertyIfAbsent(eventId); }}

          屬性包收集器

          如果您想向日志事件添加多個屬性,請使用PropertyBagEnricher

          public class PropertyBagEnricher : ILogEventEnricher{    private readonly Dictionary<string, Tuple<object, bool>> _properties;
          /// <summary> /// Creates a new <see cref="PropertyBagEnricher" /> instance. /// </summary> public PropertyBagEnricher() { _properties = new Dictionary<string, Tuple<object, bool>>(StringComparer.OrdinalIgnoreCase); }
          /// <summary> /// Enriches the <paramref name="logEvent" /> using the values from the property bag. /// </summary> /// <param name="logEvent">The log event to enrich.</param> /// <param name="propertyFactory">The factory used to create the property.</param> public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) { foreach (KeyValuePair<string, Tuple<object, bool>> prop in _properties) { logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(prop.Key, prop.Value.Item1, prop.Value.Item2)); } }
          /// <summary> /// Add a property that will be added to all log events enriched by this enricher. /// </summary> /// <param name="key">The property key.</param> /// <param name="value">The property value.</param> /// <param name="destructureObject"> /// Whether to destructure the value. See https://github.com/serilog/serilog/wiki/Structured-Data /// </param> /// <returns>The enricher instance, for chaining Add operations together.</returns> public PropertyBagEnricher Add(string key, object value, bool destructureObject = false) { if (string.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key));
          if (!_properties.ContainsKey(key)) _properties.Add(key, Tuple.Create(value, destructureObject));
          return this; }}

          用法:

          _logger    .ForContext(      new PropertyBagEnricher()        .Add("ResponseCode", response?.ResponseCode)        .Add("EnrollmentStatus", response?.Enrolled)    )    .Warning("Malfunction when processing 3DS enrollment verification");

          收集請求日志

          Serilog 請求日志記錄中間件允許提供一個函數(shù),該函數(shù)可用于將來自 HTTP 請求的附加信息添加到完成日志事件。我們使用它來記錄ClientIP,UserAgentResource屬性:

          public static class LogEnricher{    /// <summary>    /// Enriches the HTTP request log with additional data via the Diagnostic Context    /// </summary>    /// <param name="diagnosticContext">The Serilog diagnostic context</param>    /// <param name="httpContext">The current HTTP Context</param>    public static void EnrichFromRequest(IDiagnosticContext diagnosticContext, HttpContext httpContext)    {        diagnosticContext.Set("ClientIP", httpContext.Connection.RemoteIpAddress.ToString());        diagnosticContext.Set("UserAgent", httpContext.Request.Headers["User-Agent"].FirstOrDefault());        diagnosticContext.Set("Resource", httpContext.GetMetricsCurrentResourceName());    }}

          用法

          app.UseSerilogRequestLogging(opts  => opts.EnrichDiagnosticContext = LogEnricher.EnrichFromRequest);

          ? 2021 Ben Foster https://benfoster.io/

          References

          [1] Serilog: https://serilog.net/
          [2] 接收器: https://github.com/serilog?q=sinks&type=&language=
          [3] Serilog Timings: https://github.com/nblumhardt/serilog-timings
          [4] Serilog 請求日志記錄: https://github.com/serilog/serilog-aspnetcore
          [5] Serilog時序庫: https://github.com/nblumhardt/serilog-timings
          [6] Serilog 請求日志記錄中間件: https://github.com/serilog/serilog-aspnetcore
          [7] 日志上下文的: https://github.com/serilog/serilog/wiki/Enrichment
          [8] 環(huán)境收集器: https://github.com/serilog/serilog-enrichers-environment
          [9] Serilog wiki: https://github.com/serilog/serilog/wiki/Enrichment#the-logcontext
          [10] Serilog.Settings.Configuration: https://github.com/serilog/serilog-settings-configuration
          [11] 性能產(chǎn)生重大影響: https://weblog.west-wind.com/posts/2018/Dec/31/Dont-let-ASPNET-Core-Default-Console-Logging-Slow-your-App-down
          [12] Seq: https://datalust.co/seq
          [13] Seq): https://datalust.co/seq
          [14] Murmerhash 算法: https://github.com/darrenkopp/murmurhash-net
          [15] 收集器: https://github.com/darrenkopp/murmurhash-net



          往期精彩回顧




          【推薦】.NET Core開發(fā)實(shí)戰(zhàn)視頻課程 ★★★

          .NET Core實(shí)戰(zhàn)項(xiàng)目之CMS 第一章 入門篇-開篇及總體規(guī)劃

          【.NET Core微服務(wù)實(shí)戰(zhàn)-統(tǒng)一身份認(rèn)證】開篇及目錄索引

          Redis基本使用及百億數(shù)據(jù)量中的使用技巧分享(附視頻地址及觀看指南)

          .NET Core中的一個接口多種實(shí)現(xiàn)的依賴注入與動態(tài)選擇看這篇就夠了

          10個小技巧助您寫出高性能的ASP.NET Core代碼

          用abp vNext快速開發(fā)Quartz.NET定時任務(wù)管理界面

          在ASP.NET Core中創(chuàng)建基于Quartz.NET托管服務(wù)輕松實(shí)現(xiàn)作業(yè)調(diào)度

          現(xiàn)身說法:實(shí)際業(yè)務(wù)出發(fā)分析百億數(shù)據(jù)量下的多表查詢優(yōu)化

          關(guān)于C#異步編程你應(yīng)該了解的幾點(diǎn)建議

          C#異步編程看這篇就夠了


          瀏覽 44
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本黄色视频在线免费观看 | 欧美做爱视频在线 | 2026国产精品 | 亚洲国产成人无码a在线播放 | 99热网|