ASP.NET Core - 日志记录系统(一)
ASP.NET Core - 日志记录系统(一)
- 一、日志记录
- 二、ASP.Net Core 的日志记录
- 2.1. 日志记录系统的接入
- 2.2 记录日志
- 2.3 基本配置
- 2.3.1 日志级别
- 2.3.2 全局输出配置
- 2.3.3 针对特定日志提供程序的配置
- 2.3.6 显式设置
- 2.3.4 配置筛选原理
- 2.3.5 日志作用域
一、日志记录
日志记录是什么?简单而言,就是通过一些方式记录应用程序运行中的某一时刻的状态,保留应用程序当时的信息。这对于我们进行应用程序的分析、审计以及维护有很大的作用。
作为程序员,我们恐怕谁也不敢保证我们开发的软件应用一定不存在BUG,一定不会出现故障,而当故障出现的时候,日志就是我们排查故障的首要依据,排查故障的第一步一定是查看故障发生时的日志信息。
当然,日志也不仅仅只是在排查故障的时候有用,这类称为错误日志,比较常谈的还有安全日志、审计日志等等,它根据应用场景、企业团队对其认知和需要有不同的应用。日志记录在软件工程中更是一种思想,而不止是一种开发技术实现,它被认为是产品团队对其产品需求没有特别要求的非功能性使用场景,在软件框架、开发实现中基本是一种必备的横切功能点,现在的各种开发语言、框架中基本都具备日志记录的实现。
二、ASP.Net Core 的日志记录
.NET Core 框架中内置了日志记录系统,支持通过统一的 API 进入日志的记录,并且支持通过配置各种日志提供程序以各种不同的方式保存日志信息,不仅有多种内置的日志提供程序,也兼容各种按照标准规范实现的第三方框架。以下演示代码基于 .NET 7 。
2.1. 日志记录系统的接入
当我们通过 VS 应用模板创建一个 ASP.NET Core 的应用时,默认将日志记录系统添加到应用中,内部实际上是在创建 HostApplicationBuilder
的过程中,通过 AddLogging()
注册了日志相关的服务,并配置了 Console
、Debug
、EventSource
和 EventLog
(仅Windows)共四种日志记录提供程序。
var builder = WebApplication.CreateBuilder(args);
除此之外,我们也可以引入 Microsoft.Extensions.Hosting
包,自行通过通用主机创建应用,以下代码中也默认添加了日志记录系统:
var host = Host.CreateDefaultBuilder().Build();
通过查看源码,可以看到:
这两种方式最终都是通过 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;}
}
当调用该接口的时候,可以看到控制台中输出了我们记录的日志内容:
这里没有输出 Trace
和 Debug
日志,是因为默认配置中输出的最低日志级别是 Information
,要使 Trace
和 Debug
这两类日志可以正常输出,需要我们进行配置。
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 全局输出配置
我们可以在记录日志的时候指定日志的级别,但是并不是我们记录的任何一个级别的日志都会输出保存,还得配合日志记录系统的配置,就像上面的例子中,最开始 Debug
和 Trace
级别的日志是不输出的。
日志记录配置通常通过配置文件进行设置,在 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}.json
和 appsettings.json
之间的关系一样,Logging.{Provider}.LogLevel
中的配置将会覆盖 Logging.LogLevel
中的配置。例如 Logging.Console.LogLevel.Default
将会覆盖 Logging.LogLevel.Default
,Console
日志记录器将默认记录 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
根据不同的日志记录提供程序,将会:
- 查找匹配该日志记录提供程序的配置。如果找不到,则使用通用配置。
- 然后匹配拥有最长前缀的配置类别。如果找不到,则使用Default配置。
- 如果匹配到了多条配置,则采用最后一条。
- 如果没有匹配到任何配置,则使用 MinimumLevel,这是个配置项,默认是LogLevel.Information。
如上面讲到的,我们可以使用 SetMinimumLevel
方法设置 MinimumLevel
。
对应到的 .NET Core 中的源码是这一段:
在创建 ILoggerFactory
实例、创建 ILogger
实例和配置刷新的时候,都会对每一个提供程序的配置规则根据优先级进行筛选,只有最小级别不为 None
,才会创建最终的日志记录书写器,否则甚至不会有书写器。
而在具体的规则过滤逻辑中,可以看到微软的注释:
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 levelstring? 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 providerif (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
中的规则是怎么来的呢?在通过主机构建应用的时候会通过配置文件加载相关的配置,并将配置转化为规则
最终,配置文件中的每一个日志类别的配置都会结合日志提供程序转化为一项规则,默认的 LogLevel
中的配置转换成的规则中 ProviderName
为 null
,默认的 Default
类别对应的规则 CategoryName
为 null
。
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 defaultsLoadRules(options, configurationSection, null);}else{IConfigurationSection logLevelSection = configurationSection.GetSection(LogLevelKey);if (logLevelSection != null){// Load logger specific rulesstring 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 中是不会有低于配置的级别的日志的),如果都不通过,则不会转到最终的记录器。
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"}}}
}
从最终的输出中可以看到,同一个作用域中记录的日志都带上了创建作用域时设置的标记。同时也可以看到,记录的日志中多了 SpanId、TraceId、ParentId 这些内容,这是日志记录系统隐式创建范围对象,这些信息源自于每一次的Http 请求,方便对一次 Http 请求中各个步骤的跟踪。对于这些信息的配置,可以通过 ActivityTrackingOptions
设置。
var builder = WebApplication.CreateBuilder(args);
builder.Logging.Configure(option =>
{option.ActivityTrackingOptions = ActivityTrackingOptions.SpanId | ActivityTrackingOptions.TraceId;
});
以下是一些注意点:
- 若要在
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 - 日志记录系统(二)
相关文章:
ASP.NET Core - 日志记录系统(一)
ASP.NET Core - 日志记录系统(一) 一、日志记录二、ASP.Net Core 的日志记录2.1. 日志记录系统的接入2.2 记录日志2.3 基本配置2.3.1 日志级别2.3.2 全局输出配置2.3.3 针对特定日志提供程序的配置2.3.6 显式设置2.3.4 配置筛选原理2.3.5 日志作用域 一、…...
Linux 各个服务启动命令
目录 redis后台启动rocketMq后台启动mongodb后台启动mysql后台启动 redis后台启动 ./redis-server ./redis.confrocketMq后台启动 #关闭Nameserver sh bin/mqshutdown namesrv #关闭Broker sh bin/mqshutdown broker #启动namesrv nohup sh bin/mqnamesrv -n 127.0.0.1:9876 …...
24-25-1-单片机开卷部分习题和评分标准
依据相关规定试卷必须按评分标准进行批改。 给分一定是宽松的,能给分一定给,如有疑问也可以向学院教务办申请查卷。 一部分学生期末成绩由于紧张或其他原因导致分数过低,也是非常非常遗憾的。 个人也是非常抱歉的。 开卷考试 简答题 第一…...
Apache Hop从入门到精通 第二课 Apache Hop 核心概念/术语
1、apache hop核心概念思维导图 虽然apache hop是kettle的一个分支,但是它的概念和kettle还是有一些区别的,下图是我根据官方文档梳理的appache hop的核心概念思维导图。 2、Tools(工具) 1)Hop Conf Hop Conf 是一个…...
网络安全 | Web安全常见漏洞和防护经验策略
关注:CodingTechWork 引言 OWASP (Open Web Application Security Project) Top 10是Web应用最常见的安全风险集合,帮助开发人员和安全专家识别和防止最严重的网络安全问题。以下是基于OWASP Top 10的Web安全防护经验策略与规则集。Web开发者必须对潜在…...
Unity 3D游戏开发从入门进阶到高级
本文精心整理了Unity3D游戏开发相关的学习资料,涵盖入门、进阶、性能优化、面试和书籍等多个维度,旨在为Unity开发者提供全方位、高含金量的学习指南.欢迎收藏。 学习社区 Unity3D开发者 这是一个专注于Unity引擎的开发者社区,汇聚了众多Un…...
浅谈云计算14 | 云存储技术
云存储技术 一、云计算网络存储技术基础1.1 网络存储的基本概念1.2云存储系统结构模型1.1.1 存储层1.1.2 基础管理层1.1.3 应用接口层1.1.4 访问层 1.2 网络存储技术分类 二、云计算网络存储技术特点2.1 超大规模与高可扩展性2.1.1 存储规模优势2.1.2 动态扩展机制 2.2 高可用性…...
No.1|Godot|俄罗斯方块复刻|棋盘和初始方块的设置
删掉基础图标新建assets、scenes、scripts文件夹 俄罗斯方块的每种方块都是由四个小方块组成的,很适合放在网格地图中 比如网格地图是宽10列,高20行 要实现网格的对齐和下落 Node2D节点 新建一个Node2D 添加2个TileMapLayer 一个命名为Board&…...
二 RK3568 固件中打开 ADB 调试
一 usb adb Android 系统,设置->开发者选项->已连接到计算机 打开,usb调试开关打开 通过 usb otg 口连接 开发上位机 (windows/linux) 上位机安装 adb 服务之后 , 通过 cmd/shell: #1 枚举设备 adb devices #2 进入 android shell adb shell # 3 验证上传下载…...
鸿蒙报错Init keystore failed: keystore password was incorrect
报错如下: > hvigor ERROR: Failed :entry:defaultSignHap... > hvigor ERROR: Tools execution failed. 01-13 16:35:55 ERROR - hap-sign-tool: error: Init keystore failed: keystore password was incorrect * Try the following: > The key stor…...
Java学习笔记(二十三)
1 CacheEvict CacheEvict是Spring框架中用于清空缓存的注解。以下是对CacheEvict注解的详细介绍: 1.1 作用 CacheEvict注解的主要作用是删除缓存中的数据。在方法执行后或执行前(根据配置),它可以清空指定的缓存项或整个缓存区…...
TIOBE编程语言排行靠前的编程语言的吉祥物
Python的吉祥物:小蟒蛇 Python语言的吉祥物是一只名叫"Pythonidae"(或简称"Py")的小蟒蛇。这个吉祥物由Tobias Kohn设计于2005年,它的形象借鉴了真实的蟒蛇,但加入了一些可爱和友善的特点。小蟒蛇…...
Redis集群部署详解:主从复制、Sentinel哨兵模式与Cluster集群的工作原理与配置
集群部署形式 1、主从复制1.1 工作机制1.2 配置实现1.3 优缺点1.4 部署形式1.5 主从复制优化 2、Sentinel 哨兵模式2.1 工作机制2.2 配置实现2.3 优缺点2.4 哨兵机制选举流程2.5 脑裂问题解决方案 3、Redis Cluster3.1 工作机制3.2 配置实现3.3 优缺点3.4 故障转移3.5 哈希槽为…...
Dubbo泛化调用
本文记录下利用dubbo泛化调用实现网关server收http请求,然后转发给dubbo服务,然后收到dubbo响应的功能原理。 关键点1:dubbo泛化调用。可根据(注册中心地址、接口名,方法名,参数类型)唯一确定一个dubbo服务…...
SpringBoot工程快速启动
1.问题导入 以后我们和前端开发人员协同开发,而前端开发人员需要测试前端程序就需要后端开启服务器,这就受制于后端开发人员。 为了摆脱这个受制,前端开发人员尝试着在自己电脑上安装 Tomcat 和 Idea ,在自己电脑上启动后端程序&a…...
Docker实践:部署Docker管理工具DockerUI
Docker实践:部署Docker管理工具DockerUI 前言一、DockerUI介绍1.1 DockerUI概述1.2 镜像说明 二、检查本地Docker环境三、拉取DockerUI镜像四、创建DockerUI容器五、访问DockerUI六、DockerUI的基本使用6.1 查询宿主机容器情况6.2 查询Docker镜像列表6.3 查看容器配…...
【优先算法】滑动窗口--(结合例题讲解解题思路)(C++)
目录 1. 例题1:最大连续1的个数 1.1 解题思路 1.2代码实现 1.3 错误示范如下:我最开始写了一种,但是解答错误,请看,给大家做个参考 2. 将 x 减到 0 的最小操作数 2.1解题思路 2.2代码实现 1. 例题1ÿ…...
嵌入式系统Linux实时化(四)Xenomai应用开发测试
1、Xenomai 原生API 任务管理 Xenomai 本身提供的一系列多任务调度机制,主要有以下一些函数: int rt_task_create (RT_TASK task, const char name, int stksize, int prio, intmode) ; 任务的创建;int rt_task_start(RT_TASK task, void(entry)(void cookie), void cookie…...
深度剖析RabbitMQ:从基础组件到管理页面详解
文章目录 一、简介二、Overview2.1 Overview->Totals2.2 Overview->Nodesbroker的属性2.3 Overview->Churn statistics2.4 Overview->Ports and contexts2.5 Overview->Export definitions2.6 Overview->Import definitions 三、Connections连接的属性 四、C…...
算法每日双题精讲 —— 二分查找(二分查找,在排序数组中查找元素的第一个和最后一个位置)
🌟快来参与讨论💬,点赞👍、收藏⭐、分享📤,共创活力社区。 🌟 别再犹豫了!快来订阅我们的算法每日双题精讲专栏,一起踏上算法学习的精彩之旅吧!💪…...
Golang—— error 和 panic
本文详细介绍Golang的两种错误处理机制:error 和 panic。 文章目录 Golang 的错误处理机制概述error特点代码示例基本用法创建 error panic特点运行时错误示例defer 和 recover 的结合使用代码示例基本用法创建 panic panic 的执行机制 error 和 panic 的对比生产环…...
xcrun: error: invalid active developer path 解决
在拉取 github 代码时,提示如下报错: xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun 原因是:这是由于 Xcode command line t…...
Jmeter配置服务代理器 Proxy(二)
1.创建脚本记录器 2.配置:Jmeter代理、端口、记录目标等 3.配置谷歌浏览器代理 浏览器配置代理的详细教程可参考:使用whistle代理-CSDN博客 4.启动Jmeter记录器 点击ok后弹出这个界面,生成了证书: 5.给浏览器安装Jmeter代理的证书…...
小黑工具人日常积累中:sqlserver中切割字符串,取首个子串
SELECTSUBSTRING(表名字, 1, CHARINDEX(分隔符, 字段名) - 1) AS FirstPart FROM表名字 WHERECHARINDEX(分隔符, 字段名) > 0继续尝试: 提取第二个子串 窗口函数...
港科夜闻 | 香港科大与微软亚洲研究院签署战略合作备忘录,推动医学健康教育及科研协作...
关注并星标 每周阅读港科夜闻 建立新视野 开启新思维 1、香港科大与微软亚洲研究院签署战略合作备忘录,推动医学健康教育及科研协作。根据备忘录,双方将结合各自于科研领域的优势,携手推动医学健康领域的交流与合作。合作方向将涵盖人才培训、…...
掌握Golang strings包:高效字符串处理指南
掌握Golang strings包:高效字符串处理指南 引言为什么要学习和掌握strings包本教程的目标 基本用法strings包概述导入strings包常用函数列表及简要介绍 字符串创建与基本操作创建字符串字符串连接:Join重复字符串:Repeat修改字符串࿱…...
关于husky8.0 与 4.0的配置
husky的场景使用很多,一般大多场景是在配置git commit 命令拦截hook, 校验 commit-msg 格式规范。以下环境默认:git > 2.27.0, node >14 1、安装huskey8.0.1 npm install --save-dev husky8.0.1 2、初始化配置文件 在package.json scripts 属性…...
【RK3588 Linux 5.x 内核编程】-Linux内核数据结构详解(双向链表、基数树、位数组)
Linux内核数据结构详解(双向链表、基数树、位数组) 文章目录 Linux内核数据结构详解(双向链表、基数树、位数组)1、双向链表2、基数树3、位数组3.1 Linux内核中的位数组和位操作3.2 位数组声明3.3 特定于架构的位操作3.4 常见的位操作Linux内核提供了不同的数据结构实现,如…...
3.Qt Quick-QML地图引擎之v4.3版本(新增动态轨迹线/海图/天地图街道/天地图卫星)
在上个版本Qt Quick-QML地图引擎之v4版本(新增多模型切换/3D模型欧拉角模拟)_qt加载3d地图-CSDN博客更新了3D模拟功能,在4.3版本增加动态轨迹线、三个地图(海图/天地图街道/天地图卫星)。 4.3版本已经支持qt6 cmake版本,而4.3版本以下支持qt5版本&#x…...
使用WebdriverIO和Appium测试App
1.新建项目 打开Webstorm新建项目 打开终端输入命令 npm init -y npm install wdio/cli allure-commandline --save-dev npx wdio config 然后在终端依次选择如下: 然后在终端输入命令: npm install wdio/local-runnerlatest wdio/mocha-frameworkla…...
前端组件开发:组件开发 / 定义配置 / 配置驱动开发 / 爬虫配置 / 组件V2.0 / form表单 / table表单
一、最早的灵感 最早的灵感来自sprider / 网络爬虫 / 爬虫配置,在爬虫爬取网站文章时候,会输入给爬虫一个配置文件,里边的内容是一个json对象。里边包含了所有想要抓取的页面的信息。爬虫通过这个配置就可以抓取目标网站的数据。其实本文要引…...
工具推荐:PDFgear——免费且强大的PDF编辑工具 v2.1.12
PDFgear——免费且强大的PDF编辑工具 v2.1.12 软件简介 PDFgear 是一款 完全免费的 PDF 软件,支持 阅读、编辑、转换、合并 以及 跨设备签署 PDF 文件,无需注册即可使用。它提供了丰富的 PDF 处理功能,极大提升了 PDF 文件管理的便捷性和效…...
【Unity3D】【已解决】TextMeshPro无法显示中文的解决方法
TextMeshPro无法显示中文的解决方法 现象解决方法Assets 目录中新建一个字体文件夹在C:\Windows\Fonts 中随便找一个中文字体的字体文件把字体文件拖到第一步创建的文件夹中右键导入的字体,Create---TextMeshPro---Font Asset,创建字体文件资源把 SDF文件…...
【Unity】使用UniRx来快速完成Unity中的信号层开发工作。
访问官方 网址:https://github.com/neuecc/UniRx/ UniRx(Unity反应式扩展)是.NET 反应式扩展的重新实现。官方 Rx 实现很棒,但在 Unity 上不起作用,并且存在 iOS IL2CPP 兼容性问题。此库修复了这些问题,并…...
FPGA工程师成长四阶段
朋友,你有入行三年、五年、十年的职业规划吗?你知道你所做的岗位未来该如何成长吗? FPGA行业的发展近几年是蓬勃发展,有越来越多的人才想要或已经踏进了FPGA行业的大门。很多同学在入行FPGA之前,都会抱着满腹对职业发…...
配置Kubernetes从节点与集群Calico网络
在上一篇博客中,我们成功安装并初始化了Kubernetes的主节点,并且看到了集群初始化成功的标志信息。接下来,我们将继续安装从节点(worker nodes),以构建一个完整的Kubernetes集群。 步骤回顾 在上一步中&a…...
【SH】Xiaomi9刷Windows10系统研发记录 、手机刷Windows系统教程、小米9重装win10系统
文章目录 参考资料云盘资料软硬件环境手机解锁刷机驱动绑定账号和设备解锁手机 Mindows工具箱安装工具箱和修复下载下载安卓和woa资源包第三方Recovery 一键安装Windows准备工作创建分区安装系统 效果展示Windows和Android一键互换Win切换安卓安卓切换Win 删除分区 参考资料 解…...
【Qt】01-了解QT
踏入QT的殿堂之路 前言一、创建工程文件1.1 步骤介绍1.2 编译介绍方法1、方法2、编译成功 二、了解框架2.1 main.cpp2.2 .Pro文件2.2.1 注释需要打井号。2.2.2 F1带你进入帮助模式2.2.3 build文件 2.3 构造函数 三、编写工程3.1 main代码3.2 结果展示 四、指定父对象4.1 main代…...
STC的51单片机LED点灯基于KEIL
前言: 该文源于回答一个朋友的问题,代码为该朋友上传,略作修改,在此说明问题以及解决问题的思路,以减少新手错误。 电路图: 该位朋友未上传电路图,说明如下: stc8g1k08a-sop8控制…...
AV1视频编解码简介、码流结构(OBU)
我的音视频/流媒体开源项目(github) 目录 一、AV1编码技术 二、AV1码流结构(OBU) 三、IVF文件格式 四、ffmpeg支持AV1 五、关于常见格式对AV1的封装 一、AV1编码技术 AV1是由开放媒体联盟(AOM,Alliance for Open Media)在2018年发布的,AV1的前身…...
Service Work离线体验与性能优化
Service Work离线体验与性能优化 引言 先放个意外事件,万事开头难🤣🤣🤣 原计划是分享离线应用与数据资源缓存的应用实践,结果发现这一技术已被web标准废弃 曾经做过一个PC应用,业务需求要求应用具备容灾…...
linux之进程信号(初识信号,信号的产生)
目录 引入一、初识信号(信号预备知识)1.生活中的信号2.Linux中的信号3.信号进程得出的初步结论 二、信号的产生1.通过终端输入产生信号拓展: 硬件中断2.调用系统函数向进程发信号3.硬件异常产生信号4.软件条件产生信号拓展: 核心转储技术总结一下: 引入 一、初识信…...
为深度学习创建PyTorch张量 - 最佳选项
为深度学习创建PyTorch张量 - 最佳选项 正如我们所看到的,PyTorch张量是torch.Tensor PyTorch类的实例。张量的抽象概念与PyTorch张量之间的区别在于,PyTorch张量为我们提供了一个可以在代码中操作的具体实现。 在上一篇文章中,我们看到了…...
MySQL 与 Redis 数据一致性 2
1. 强一致还是最终一致?2. 先写 MySQL 还是先写Redis?case 1 3. 缓存(Redis)更新还是清除?更新策略更新策略会有数据不一致问题?数据不一致的概率与影响如果使用监听binlog更新数据还会出现数据不一致问题?binlog的消费问题 使用消息队列行不行?其他方案总结: 数据不一致…...
Git | git reset命令详解
关注:CodingTechWork 引言 Git 是一款非常流行的分布式版本控制工具,它帮助开发者有效地管理代码历史,支持多种功能来帮助团队协作、追踪修改和维护代码质量。git reset是 Git 中最强大、最复杂的命令之一,它的主要作用是重置当前…...
Linux高并发服务器开发 第十四天(dup/duo2/fcntl 进程 pcb进程控制块 环境变量)
目录 1.dup 和 dup2 1.1dup 1.2dup2 2.fcntl 3.进程 3.1进程和程序 3.2并发 3.3cpu 3.5pcb进程控制块 3.6进程状态 4.环境变量 1.dup 和 dup2 1.1dup - 将 文件描述符 ,复制产生“新文件描述符” 并返回。新、旧文件描述符,指向同一文件。 …...
[MySQL | 二、基本数据类型]
基本数据类型 一、数值类型举例表结构1. 整数类型zerofill属性 与 int(n) 中 n 的关系 2.bit类型3. 小数类型float类型decimal类型 二、字符串类型1. char2. varchar如何选择定长或变长字符串? 3. 日期时间类型(date datetime timestamp)4. enum枚举类型5. set多选类…...
第G1周:生成对抗网络(GAN)入门
>- **🍨 本文为[🔗365天深度学习训练营]中的学习记录博客** >- **🍖 原作者:[K同学啊]** 本人往期文章可查阅: 深度学习总结 基础任务 1.了解什么是生成对抗网络2.生成对抗网络结构是怎么样的3.学习本文代码&am…...
ROS2 准备工作(虚拟机安装,Ubuntu安装,ROS2系统安装)
准备工作 虚拟机安装 大家可以自行去安装VMware链接:https://pan.baidu.com/s/1KcN1I9FN--Sp1bUsjKqWVA?pwd6666 提取码:6666(提供者:零基础编程入门教程) 教程:【【2025最新版】VMware虚拟机安装教程,手把手教你免…...
FreeType 介绍及 C# 示例
FreeType 是一个开源的字体渲染引擎,用于将字体文件(如 TrueType、OpenType、Type 1 等)转换为位图或矢量图形。它广泛应用于操作系统、图形库、游戏引擎等领域,支持高质量的字体渲染和复杂的文本布局。 FreeType 的核心功能 字体…...