├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── build-and-publish.yml │ ├── build-and-test.yml │ └── snyk-test.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Examples ├── Example.Logging.Appsettings │ ├── Controllers │ │ └── WeatherForecastController.cs │ ├── Example.Logging.Appsettings.csproj │ ├── Program.cs │ ├── Startup.cs │ ├── WeatherForecast.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── Example.Logging.AspNetCore │ ├── Controllers │ │ └── WeatherForecastController.cs │ ├── Example.Logging.AspNetCore.csproj │ ├── Program.cs │ ├── Startup.cs │ ├── WeatherForecast.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── Example.Logging.Microsoft.Extensions │ ├── Controllers │ │ └── WeatherForecastController.cs │ ├── Example.Logging.Microsoft.Extensions.csproj │ ├── Program.cs │ ├── Startup.cs │ ├── WeatherForecast.cs │ ├── appsettings.Development.json │ └── appsettings.json └── Example.Logging │ ├── Controllers │ └── WeatherForecastController.cs │ ├── Example.Logging.csproj │ ├── Program.cs │ ├── Startup.cs │ ├── WeatherForecast.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── LICENSE.md ├── README.md ├── RockLib.Logging.All.sln ├── RockLib.Logging.AspNetCore ├── .editorconfig ├── CHANGELOG.md ├── CorrelationIdContextProvider.cs ├── CorrelationIdContextProviderOptions.cs ├── Directory.Build.props ├── ForwardedForContextProvider.cs ├── HeaderNames.cs ├── HttpContextExtensions.cs ├── HttpContextProvider.cs ├── InfoLogAttribute.cs ├── LogEntryExtensions.cs ├── LoggerBuilderExtensions.cs ├── LoggingActionFilterAttribute.cs ├── PathContextProvider.cs ├── ReferrerContextProvider.cs ├── RemoteIpAddressContextProvider.cs ├── RequestMethodContextProvider.cs ├── RockLib.Logging.AspNetCore.csproj ├── RockLib.Logging.AspNetCore.sln ├── RouteNotFoundMiddleware.cs ├── RouteNotFoundMiddlewareExtensions.cs ├── RouteNotFoundMiddlewareOptions.cs └── UserAgentContextProvider.cs ├── RockLib.Logging.Microsoft.Extensions ├── .editorconfig ├── CHANGELOG.md ├── Directory.Build.props ├── RockLib.Logging.Microsoft.Extensions.csproj ├── RockLib.Logging.Microsoft.Extensions.sln ├── RockLibLogger.cs ├── RockLibLoggerOptions.cs ├── RockLibLoggerProvider.cs ├── RockLibLoggerProviderExtensions.cs └── Shims.cs ├── RockLib.Logging.Moq ├── .editorconfig ├── CHANGELOG.md ├── Directory.Build.props ├── MockLogger.cs ├── MockLoggerExtensions.VerifyLog.cs ├── MockLoggerExtensions.cs ├── RockLib.Logging.Moq.csproj └── RockLib.Logging.Moq.sln ├── RockLib.Logging ├── .editorconfig ├── AssemblyAttributes.cs ├── CHANGELOG.md ├── Cached.cs ├── DependencyInjection │ ├── ILoggerBuilder.cs │ ├── LoggerBuilder.cs │ ├── LoggerBuilderExtensions.cs │ ├── LoggerLookup.cs │ ├── Options │ │ ├── ConsoleLogProviderOptions.cs │ │ ├── DebugLogProviderOptions.cs │ │ ├── FileLogProviderOptions.cs │ │ ├── FormattableLogProviderOptions.cs │ │ ├── ILoggerOptions.cs │ │ ├── LogProviderOptions.cs │ │ ├── LoggerOptions.cs │ │ └── RollingFileLogProviderOptions.cs │ ├── ReloadingLogProvider.cs │ ├── ReloadingLogger.cs │ └── ServiceCollectionExtensions.cs ├── Diagnostics │ └── LoggingTraceListener.cs ├── Directory.Build.props ├── Error.cs ├── Extensions │ ├── EnabledExtensions.cs │ ├── ErrorHandlerExtensions.cs │ └── LoggingExtensions.cs ├── FormatToString │ ├── BlockIndentExtension.cs │ └── FormatToStringExtension.cs ├── IContextProvider.cs ├── IErrorHandler.cs ├── ILogger.cs ├── LogEntry.cs ├── LogLevel.cs ├── LogProcessing │ ├── BackgroundLogProcessor.cs │ ├── FireAndForgetLogProcessor.cs │ ├── ILogProcessor.cs │ ├── LogProcessor.cs │ └── SynchronousLogProcessor.cs ├── LogProviders │ ├── ConsoleLogProvider.cs │ ├── DebugLogProvider.cs │ ├── FileLogProvider.cs │ ├── ILogFormatter.cs │ ├── ILogLevelResolver.cs │ ├── ILogProvider.cs │ ├── RollingFileLogProvider.cs │ ├── RolloverPeriod.cs │ └── TemplateLogFormatter.cs ├── Logger.cs ├── LoggerFactory.cs ├── LoggerFactoryExtensions.cs ├── RockLib.Logging.csproj ├── RockLib.Logging.sln └── SafeLogging │ ├── NotSafeToLogAttribute.cs │ ├── SafeLoggingExtensions.cs │ ├── SafeToLogAttribute.cs │ └── SanitizeEngine.cs ├── Tests ├── RockLib.Logging.AspNetCore.Tests │ ├── .editorconfig │ ├── ContextProviderTests.cs │ ├── Directory.Build.props │ ├── InfoLogAttributeTests.cs │ ├── LogBuilderExtensionTests.cs │ ├── LogEntryExtensionsTests.cs │ ├── LoggingActionFilterAttributeTests.cs │ ├── RockLib.Logging.AspNetCore.Tests.csproj │ ├── RouteNotFoundMiddlewareExtensionsTests.cs │ └── RouteNotFoundMiddlewareTests.cs ├── RockLib.Logging.Microsoft.Extensions.Tests │ ├── .editorconfig │ ├── Directory.Build.props │ ├── RockLib.Logging.Microsoft.Extensions.Tests.csproj │ ├── RockLibLoggerProviderExtensionsTests.cs │ ├── RockLibLoggerProviderTests.cs │ └── RockLibLoggerTests.cs ├── RockLib.Logging.Moq.Tests │ ├── .editorconfig │ ├── AssemblyInformation.cs │ ├── Directory.Build.props │ ├── MockLoggerExtensionsTests.cs │ ├── MockLoggerTests.cs │ └── RockLib.Logging.Moq.Tests.csproj └── RockLib.Logging.Tests │ ├── .editorconfig │ ├── DelegateErrorHandler.cs │ ├── DependencyInjection │ ├── LoggerBuilderExtensionsTests.cs │ ├── LoggerBuilderTests.cs │ ├── Options │ │ ├── FileLogProviderOptionsTests.cs │ │ ├── FormattableLogProviderOptionsTests.cs │ │ └── RollingFileLogProviderOptionsTests.cs │ ├── ReloadingLogProviderTests.cs │ ├── ReloadingLoggerTests.cs │ ├── ServiceCollectionExtensionsTests.cs │ ├── TestConfigurationProvider.cs │ └── TestConfigurationSource.cs │ ├── Diagnostics │ └── LoggingTraceListenerTests.cs │ ├── Directory.Build.props │ ├── LogEntryExceptionTests.cs │ ├── LogEntryTests.cs │ ├── LogProcessingTests │ ├── BackgroundLogProcessorTests.cs │ ├── FakeLogProvider.cs │ ├── FireAndForgetLogProcessorTests.cs │ ├── LogProcessorTests.cs │ └── SynchronousLogProcessorTests.cs │ ├── LogProviderTests │ ├── ConsoleLogProviderTests.cs │ ├── DebugLogProviderTests.cs │ ├── FileLogProviderTests.cs │ └── RollingFileLogProviderTests.cs │ ├── LoggerFactoryTests.cs │ ├── LoggerTests.cs │ ├── NullErrorHandler.cs │ ├── RockLib.Logging.Tests.csproj │ ├── SafeLogging │ └── SanitizeEngineTests.cs │ └── appsettings.json ├── docs ├── AspNetCore.md ├── ConsoleLogProvider.md ├── ContextProviders.md ├── DI.md ├── FileLogProvider.md ├── Formatting.md ├── GettingStarted.md ├── LogLevelResolver.md ├── LogProcessors.md ├── LogProviderErrors.md ├── LogProviders.md ├── Logger.md ├── LoggerFactory.md ├── LoggingStart.md ├── LoggingTraceListener.md ├── Microsoft.md ├── Moq.md ├── Reloading.md ├── RollingFileLogProvider.md ├── SafeLogging.md └── Tracing.md └── icon.png /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 12 | 13 | ## Type of change: 14 | 15 | 1. Non-functional change (e.g. documentation changes, removing unused `using` directives, renaming local variables, etc) 16 | 2. Bug fix (non-breaking change that fixes an issue) 17 | 3. New feature (non-breaking change that adds functionality) 18 | 4. Breaking change (fix or feature that could cause existing functionality to not work as expected) 19 | 20 | ## Checklist: 21 | 22 | - Have you reviewed your own code? Do you understand every change? 23 | - Are you following the [contributing guidelines](../blob/main/CONTRIBUTING.md)? 24 | - Have you added tests that prove your fix is effective or that this feature works? 25 | - New and existing unit tests pass locally with these changes? 26 | - Have you made corresponding changes to the documentation? 27 | - Will this change require an update to an example project? (if so, create an issue and link to it) 28 | 29 | --- 30 | 31 | _[Reviewer guidelines](../blob/main/CONTRIBUTING.md#reviewing-changes)_ 32 | -------------------------------------------------------------------------------- /.github/workflows/build-and-publish.yml: -------------------------------------------------------------------------------- 1 | name: Build and Publish 2 | 3 | #################################################################################################### 4 | ## WORKFLOW TRIGGER 5 | #################################################################################################### 6 | on: 7 | # Workflow will run when a release is published. 8 | release: 9 | types: [ released, prereleased ] 10 | 11 | #################################################################################################### 12 | ## WORKFLOW JOBS 13 | #################################################################################################### 14 | jobs: 15 | # Calls the shared build-and-publish workflow. 16 | call_build_and_publish: 17 | name: Call build-and-publish workflow 18 | uses: RockLib/RockLib.Workflows/.github/workflows/build-and-publish.yml@main 19 | secrets: inherit 20 | -------------------------------------------------------------------------------- /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | name: Run Unit Test 2 | 3 | #################################################################################################### 4 | ## WORKFLOW TRIGGER 5 | #################################################################################################### 6 | on: 7 | # Workflow will run on pull requests to the main branch. 8 | pull_request: 9 | branches: [ main ] 10 | 11 | #################################################################################################### 12 | ## WORKFLOW JOBS 13 | #################################################################################################### 14 | jobs: 15 | # Calls the shared unit-test workflow. 16 | call_unit_test: 17 | name: Call unit-test workflow 18 | uses: RockLib/RockLib.Workflows/.github/workflows/unit-test.yml@main 19 | secrets: inherit 20 | -------------------------------------------------------------------------------- /.github/workflows/snyk-test.yml: -------------------------------------------------------------------------------- 1 | name: Run Snyk Test 2 | 3 | #################################################################################################### 4 | ## WORKFLOW TRIGGER 5 | #################################################################################################### 6 | on: 7 | # Workflow will run after unit-test is completed. 8 | workflow_run: 9 | workflows: [ Run Unit Test ] 10 | types: [ completed ] 11 | 12 | #################################################################################################### 13 | ## WORKFLOW JOBS 14 | #################################################################################################### 15 | jobs: 16 | # Calls the shared snyk-test workflow. 17 | call_snyk_test: 18 | name: Call snyk-test workflow 19 | uses: RockLib/RockLib.Workflows/.github/workflows/snyk-test.yml@main 20 | secrets: inherit 21 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at RockLibSupport@quickenloans.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /Examples/Example.Logging.Appsettings/Controllers/WeatherForecastController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.AspNetCore.Mvc; 5 | using RockLib.Logging; 6 | using RockLib.Logging.SafeLogging; 7 | 8 | namespace Example.Logging.Controllers 9 | { 10 | [ApiController] 11 | [Route("[controller]")] 12 | public class WeatherForecastController : ControllerBase 13 | { 14 | private static readonly string[] Summaries = new[] 15 | { 16 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 17 | }; 18 | 19 | private readonly ILogger _logger; 20 | 21 | public WeatherForecastController(ILogger logger) 22 | { 23 | _logger = logger; 24 | } 25 | 26 | [HttpGet] 27 | public IEnumerable Get() 28 | { 29 | var rng = new Random(); 30 | var forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast 31 | { 32 | Date = DateTime.Now.AddDays(index), 33 | TemperatureC = rng.Next(-20, 55), 34 | Summary = Summaries[rng.Next(Summaries.Length)] 35 | }) 36 | .ToArray(); 37 | 38 | _logger.InfoSanitized("GET /WeatherForecast", new { Result = forecasts }); 39 | 40 | return forecasts; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Examples/Example.Logging.Appsettings/Example.Logging.Appsettings.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/Example.Logging.Appsettings/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace Example.Logging 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup(); 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Examples/Example.Logging.Appsettings/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Hosting; 6 | using RockLib.Logging.DependencyInjection; 7 | 8 | namespace Example.Logging 9 | { 10 | public class Startup 11 | { 12 | public Startup(IConfiguration configuration) 13 | { 14 | Configuration = configuration; 15 | } 16 | 17 | public IConfiguration Configuration { get; } 18 | 19 | // This method gets called by the runtime. Use this method to add services to the container. 20 | public void ConfigureServices(IServiceCollection services) 21 | { 22 | services.AddControllers(); 23 | 24 | services.AddLogger(); 25 | } 26 | 27 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 28 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 29 | { 30 | if (env.IsDevelopment()) 31 | { 32 | app.UseDeveloperExceptionPage(); 33 | } 34 | 35 | app.UseHttpsRedirection(); 36 | 37 | app.UseRouting(); 38 | 39 | app.UseAuthorization(); 40 | 41 | app.UseEndpoints(endpoints => 42 | { 43 | endpoints.MapControllers(); 44 | }); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Examples/Example.Logging.Appsettings/WeatherForecast.cs: -------------------------------------------------------------------------------- 1 | using RockLib.Logging.SafeLogging; 2 | using System; 3 | 4 | namespace Example.Logging 5 | { 6 | [SafeToLog] 7 | public class WeatherForecast 8 | { 9 | public DateTime Date { get; set; } 10 | 11 | public int TemperatureC { get; set; } 12 | 13 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 14 | 15 | public string Summary { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Examples/Example.Logging.Appsettings/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning", 5 | "Microsoft.Hosting.Lifetime": "Information" 6 | } 7 | }, 8 | "Rocklib.Logging": { 9 | "Level": "Info" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Examples/Example.Logging.Appsettings/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "AllowedHosts": "*", 3 | "Rocklib.Logging": { 4 | "Level": "Warn", 5 | "LogProviders": { "Type": "RockLib.Logging.ConsoleLogProvider, RockLib.Logging" } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Examples/Example.Logging.AspNetCore/Controllers/WeatherForecastController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.Extensions.Logging; 3 | using RockLib.Logging.AspNetCore; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | namespace Example.Logging.AspNetCore.Controllers 9 | { 10 | [ApiController] 11 | [Route("[controller]")] 12 | public class WeatherForecastController : ControllerBase 13 | { 14 | private static readonly string[] Summaries = new[] 15 | { 16 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 17 | }; 18 | 19 | private readonly ILogger _logger; 20 | 21 | public WeatherForecastController(ILogger logger) 22 | { 23 | _logger = logger; 24 | } 25 | 26 | [HttpGet] 27 | [InfoLog] 28 | public IEnumerable Get() 29 | { 30 | var rng = new Random(); 31 | return Enumerable.Range(1, 5).Select(index => new WeatherForecast 32 | { 33 | Date = DateTime.Now.AddDays(index), 34 | TemperatureC = rng.Next(-20, 55), 35 | Summary = Summaries[rng.Next(Summaries.Length)] 36 | }) 37 | .ToArray(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Examples/Example.Logging.AspNetCore/Example.Logging.AspNetCore.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | NU1605 4 | net8.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Examples/Example.Logging.AspNetCore/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace Example.Logging.AspNetCore 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup(); 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Examples/Example.Logging.AspNetCore/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Hosting; 6 | using RockLib.Logging.AspNetCore; 7 | using RockLib.Logging.DependencyInjection; 8 | 9 | namespace Example.Logging.AspNetCore 10 | { 11 | public class Startup 12 | { 13 | public Startup(IConfiguration configuration) 14 | { 15 | Configuration = configuration; 16 | } 17 | 18 | public IConfiguration Configuration { get; } 19 | 20 | // This method gets called by the runtime. Use this method to add services to the container. 21 | public void ConfigureServices(IServiceCollection services) 22 | { 23 | services.AddControllers(); 24 | 25 | services.AddHttpContextAccessor(); 26 | 27 | services.Configure(Configuration.GetSection("Logger")); 28 | 29 | services.AddLogger() 30 | .AddConsoleLogProvider() 31 | .AddHttpContextProvider(); 32 | } 33 | 34 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 35 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 36 | { 37 | app.UseRouteNotFoundLogging(); 38 | 39 | if (env.IsDevelopment()) 40 | { 41 | app.UseDeveloperExceptionPage(); 42 | } 43 | 44 | app.UseRouting(); 45 | 46 | app.UseAuthorization(); 47 | 48 | app.UseEndpoints(endpoints => 49 | { 50 | endpoints.MapControllers(); 51 | }); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Examples/Example.Logging.AspNetCore/WeatherForecast.cs: -------------------------------------------------------------------------------- 1 | using RockLib.Logging.SafeLogging; 2 | using System; 3 | 4 | namespace Example.Logging.AspNetCore 5 | { 6 | [SafeToLog] 7 | public class WeatherForecast 8 | { 9 | public DateTime Date { get; set; } 10 | 11 | public int TemperatureC { get; set; } 12 | 13 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 14 | 15 | public string Summary { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Examples/Example.Logging.AspNetCore/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "Logger": { 10 | "Level": "Info" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Examples/Example.Logging.AspNetCore/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*", 10 | "Logger": { 11 | "Level": "Warn" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Examples/Example.Logging.Microsoft.Extensions/Controllers/WeatherForecastController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Extensions.Logging; 6 | using Newtonsoft.Json; 7 | 8 | namespace Example.Logging.Microsoft.Extensions.Controllers 9 | { 10 | [ApiController] 11 | [Route("[controller]")] 12 | public class WeatherForecastController : ControllerBase 13 | { 14 | private static readonly string[] Summaries = new[] 15 | { 16 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 17 | }; 18 | 19 | private readonly ILogger _logger; 20 | 21 | public WeatherForecastController(ILogger logger) 22 | { 23 | _logger = logger; 24 | } 25 | 26 | [HttpGet] 27 | public IEnumerable Get() 28 | { 29 | var rng = new Random(); 30 | var forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast 31 | { 32 | Date = DateTime.Now.AddDays(index), 33 | TemperatureC = rng.Next(-20, 55), 34 | Summary = Summaries[rng.Next(Summaries.Length)] 35 | }) 36 | .ToArray(); 37 | 38 | _logger.LogInformation("Forecasts: {forecasts}", JsonConvert.SerializeObject(forecasts)); 39 | 40 | return forecasts; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Examples/Example.Logging.Microsoft.Extensions/Example.Logging.Microsoft.Extensions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/Example.Logging.Microsoft.Extensions/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | using Microsoft.Extensions.Logging; 4 | using RockLib.Logging; 5 | 6 | namespace Example.Logging.Microsoft.Extensions 7 | { 8 | public class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | CreateHostBuilder(args).Build().Run(); 13 | } 14 | 15 | public static IHostBuilder CreateHostBuilder(string[] args) => 16 | Host.CreateDefaultBuilder(args) 17 | .ConfigureWebHostDefaults(webBuilder => 18 | { 19 | webBuilder.UseStartup(); 20 | }) 21 | .ConfigureLogging(logging => 22 | { 23 | logging.ClearProviders(); 24 | logging.AddRockLibLoggerProvider(options => options.IncludeScopes = true); 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Examples/Example.Logging.Microsoft.Extensions/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Hosting; 6 | using RockLib.Logging; 7 | using RockLib.Logging.DependencyInjection; 8 | 9 | namespace Example.Logging.Microsoft.Extensions 10 | { 11 | public class Startup 12 | { 13 | public Startup(IConfiguration configuration) 14 | { 15 | Configuration = configuration; 16 | } 17 | 18 | public IConfiguration Configuration { get; } 19 | 20 | // This method gets called by the runtime. Use this method to add services to the container. 21 | public void ConfigureServices(IServiceCollection services) 22 | { 23 | services.AddControllers(); 24 | 25 | services.Configure(Configuration.GetSection("Logger")); 26 | 27 | services.AddLogger(processingMode: Logger.ProcessingMode.Synchronous) 28 | .AddConsoleLogProvider(); 29 | } 30 | 31 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 32 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 33 | { 34 | if (env.IsDevelopment()) 35 | { 36 | app.UseDeveloperExceptionPage(); 37 | } 38 | 39 | app.UseHttpsRedirection(); 40 | 41 | app.UseRouting(); 42 | 43 | app.UseAuthorization(); 44 | 45 | app.UseEndpoints(endpoints => 46 | { 47 | endpoints.MapControllers(); 48 | }); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Examples/Example.Logging.Microsoft.Extensions/WeatherForecast.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Example.Logging.Microsoft.Extensions 4 | { 5 | public class WeatherForecast 6 | { 7 | public DateTime Date { get; set; } 8 | 9 | public int TemperatureC { get; set; } 10 | 11 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 12 | 13 | public string Summary { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Examples/Example.Logging.Microsoft.Extensions/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "Logger": { 10 | "Level": "Info" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Examples/Example.Logging.Microsoft.Extensions/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "AllowedHosts": "*", 3 | "Logger": { 4 | "Level": "Warn" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/Example.Logging/Controllers/WeatherForecastController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.AspNetCore.Mvc; 5 | using RockLib.Logging; 6 | using RockLib.Logging.SafeLogging; 7 | 8 | namespace Example.Logging.Controllers 9 | { 10 | [ApiController] 11 | [Route("[controller]")] 12 | public class WeatherForecastController : ControllerBase 13 | { 14 | private static readonly string[] Summaries = new[] 15 | { 16 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 17 | }; 18 | 19 | private readonly ILogger _logger; 20 | 21 | public WeatherForecastController(ILogger logger) 22 | { 23 | _logger = logger; 24 | } 25 | 26 | [HttpGet] 27 | public IEnumerable Get() 28 | { 29 | var rng = new Random(); 30 | var forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast 31 | { 32 | Date = DateTime.Now.AddDays(index), 33 | TemperatureC = rng.Next(-20, 55), 34 | Summary = Summaries[rng.Next(Summaries.Length)] 35 | }) 36 | .ToArray(); 37 | 38 | _logger.InfoSanitized("GET /WeatherForecast", new { Result = forecasts }); 39 | 40 | return forecasts; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Examples/Example.Logging/Example.Logging.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/Example.Logging/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace Example.Logging 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup(); 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Examples/Example.Logging/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Hosting; 6 | using RockLib.Logging.DependencyInjection; 7 | 8 | namespace Example.Logging 9 | { 10 | public class Startup 11 | { 12 | public Startup(IConfiguration configuration) 13 | { 14 | Configuration = configuration; 15 | } 16 | 17 | public IConfiguration Configuration { get; } 18 | 19 | // This method gets called by the runtime. Use this method to add services to the container. 20 | public void ConfigureServices(IServiceCollection services) 21 | { 22 | services.AddControllers(); 23 | 24 | services.Configure(Configuration.GetSection("Logger")); 25 | 26 | services.AddLogger() 27 | .AddConsoleLogProvider(); 28 | } 29 | 30 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 31 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 32 | { 33 | if (env.IsDevelopment()) 34 | { 35 | app.UseDeveloperExceptionPage(); 36 | } 37 | 38 | app.UseHttpsRedirection(); 39 | 40 | app.UseRouting(); 41 | 42 | app.UseAuthorization(); 43 | 44 | app.UseEndpoints(endpoints => 45 | { 46 | endpoints.MapControllers(); 47 | }); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Examples/Example.Logging/WeatherForecast.cs: -------------------------------------------------------------------------------- 1 | using RockLib.Logging.SafeLogging; 2 | using System; 3 | 4 | namespace Example.Logging 5 | { 6 | [SafeToLog] 7 | public class WeatherForecast 8 | { 9 | public DateTime Date { get; set; } 10 | 11 | public int TemperatureC { get; set; } 12 | 13 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 14 | 15 | public string Summary { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Examples/Example.Logging/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning", 5 | "Microsoft.Hosting.Lifetime": "Information" 6 | } 7 | }, 8 | "Logger": { 9 | "Level": "Info" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Examples/Example.Logging/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "AllowedHosts": "*", 3 | "Logger": { 4 | "Level": "Warn" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (C) 2018-2021 Rocket Mortgage 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RockLib.Logging 2 | 3 | *A simple logging library.* 4 | 5 | ## Packages 6 | 7 | ### RockLib.Logging 8 | The main library. 9 | 10 | ### RockLib.Logging.AspNetCore 11 | RockLib.Logging for AspNetCore. Includes context providers and a logging action filter. 12 | 13 | ### RockLib.Logging.Microsoft.Extensions 14 | An implementation of Microsoft.Extensions.Logging that uses RockLib.Logging. 15 | 16 | ### RockLib.Logging.Moq 17 | Extensions for verifying logging operations with Moq. 18 | 19 | --- 20 | 21 | - [Getting started](docs/GettingStarted.md) 22 | - How to: 23 | - [Perform basic logging operations](docs/LoggingStart.md) 24 | - [Instantiate and configure a logger](docs/Logger.md) 25 | - [Add log extended properties "safely" (i.e. automatically remove PII)](docs/SafeLogging.md) 26 | - [Configure and use LoggerFactory](docs/LoggerFactory.md) 27 | - [Add RockLib logging to the Microsoft dependency injection system](docs/DI.md) 28 | - [Add RockLib logging to the Microsoft logging system](docs/Microsoft.md) 29 | - [Use log providers](docs/LogProviders.md) 30 | - [ConsoleLogProvider](docs/ConsoleLogProvider.md) 31 | - [FileLogProvider](docs/FileLogProvider.md) 32 | - [RollingFileLogProvider](docs/RollingFileLogProvider.md) 33 | - [Handle log provider errors](docs/LogProviderErrors.md) 34 | - [Use context providers](docs/ContextProviders.md) 35 | - [Use log processors / processing mode](docs/LogProcessors.md) 36 | - [Use log level resolver](docs/LogLevelResolver.md) 37 | - [Format logs](docs/Formatting.md) 38 | - [Enable tracing for troubleshooting](docs/Tracing.md) 39 | - [Use LoggingTraceListener to log trace messages](docs/LoggingTraceListener.md) 40 | - [Automatically capture HttpContext information in AspNetCore apps](docs/AspNetCore.md#context-providers) 41 | - [Automatically log AspNetCore controller actions](docs/AspNetCore.md#logging-action-filters) 42 | - [Automatically log 404 http responses](docs/AspNetCore.md#route-not-found-middleware) 43 | - [Change a logger's settings "on the fly" (in a running application)](docs/Reloading.md) 44 | - [Test logging in an application using RockLib.Logging.Moq](docs/Moq.md) 45 | - API Reference: 46 | - [RockLib.Logging](https://www.nuget.org/packages/RockLib.Logging) 47 | - [RockLib.Logging.AspNetCore](https://www.nuget.org/packages/RockLib.Logging.AspNetCore) 48 | - [RockLib.Logging.Microsoft.Extensions](https://www.nuget.org/packages/RockLib.Logging.Microsoft.Extensions) 49 | - [RockLib.Logging.Moq](https://www.nuget.org/packages/RockLib.Logging.Moq) 50 | -------------------------------------------------------------------------------- /RockLib.Logging.AspNetCore/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | 6 | [*.cs] 7 | # Styling 8 | indent_style = space 9 | indent_size = 4 10 | csharp_indent_case_contents = true 11 | csharp_indent_switch_labels = true 12 | csharp_new_line_before_catch = true 13 | csharp_new_line_before_else = true 14 | csharp_new_line_before_finally = true 15 | csharp_new_line_before_members_in_anonymous_types = false 16 | csharp_new_line_before_members_in_object_initializers = false 17 | csharp_new_line_before_open_brace = methods, control_blocks, types, properties, lambdas, accessors, object_collection_array_initializers 18 | csharp_new_line_between_query_expression_clauses = true 19 | csharp_prefer_braces = false:suggestion 20 | csharp_prefer_simple_default_expression = true:suggestion 21 | csharp_preferred_modifier_order = public,private,internal,protected,static,readonly,async,override,sealed:suggestion 22 | csharp_preserve_single_line_blocks = true 23 | csharp_preserve_single_line_statements = true 24 | csharp_space_after_cast = false 25 | csharp_space_after_colon_in_inheritance_clause = true 26 | csharp_space_after_keywords_in_control_flow_statements = true 27 | csharp_space_before_colon_in_inheritance_clause = true 28 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 29 | csharp_space_between_method_call_name_and_opening_parenthesis = false 30 | csharp_space_between_method_call_parameter_list_parentheses = false 31 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 32 | csharp_space_between_method_declaration_parameter_list_parentheses = false 33 | csharp_style_pattern_matching_over_as_with_null_check = true:error 34 | csharp_style_expression_bodied_accessors = true:error 35 | csharp_style_expression_bodied_constructors = true:error 36 | csharp_style_expression_bodied_methods = true:error 37 | csharp_style_expression_bodied_properties = true:error 38 | csharp_style_namespace_declarations = file_scoped:error 39 | csharp_style_inlined_variable_declaration = true:error 40 | csharp_style_var_elsewhere = true:error 41 | csharp_style_var_for_built_in_types = true:error 42 | csharp_style_var_when_type_is_apparent = true:error 43 | dotnet_sort_system_directives_first = false 44 | dotnet_style_explicit_tuple_names = true:suggestion 45 | dotnet_style_object_initializer = true:suggestion 46 | csharp_style_pattern_local_over_anonymous_function = false:error 47 | dotnet_style_predefined_type_for_member_access = true:error 48 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:error 49 | dotnet_style_prefer_inferred_tuple_names = true:error 50 | dotnet_style_predefined_type_for_locals_parameters_members = true:error 51 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:error 52 | dotnet_style_qualification_for_field = false:error 53 | dotnet_style_qualification_for_method = false:error 54 | dotnet_style_qualification_for_property = false:error 55 | 56 | # Analyzer Configuration 57 | # These are rules we want to either ignore or have set as suggestion or info 58 | 59 | # CA1014: Mark assemblies with CLSCompliant 60 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1014 61 | dotnet_diagnostic.CA1014.severity = none 62 | 63 | # CA1725: Parameter names should match base declaration 64 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1725 65 | dotnet_diagnostic.CA1725.severity = suggestion 66 | 67 | # CA2227: Collection properties should be read only 68 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2227 69 | dotnet_diagnostic.CA2227.severity = suggestion 70 | -------------------------------------------------------------------------------- /RockLib.Logging.AspNetCore/CorrelationIdContextProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.Extensions.Options; 3 | using RockLib.DistributedTracing; 4 | using RockLib.DistributedTracing.AspNetCore; 5 | using System; 6 | using static RockLib.DistributedTracing.AspNetCore.HeaderNames; 7 | 8 | namespace RockLib.Logging.AspNetCore; 9 | 10 | /// 11 | /// An implementation of used to add a correlation id to a . 12 | /// 13 | public class CorrelationIdContextProvider : IContextProvider 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The http context accessor used to retreive the correlation id. 19 | /// Options containing the name of the correlation id header. 20 | public CorrelationIdContextProvider(IHttpContextAccessor httpContextAccessor, 21 | IOptionsMonitor? options = null) 22 | : this(httpContextAccessor?.HttpContext?.GetCorrelationIdAccessor(options?.CurrentValue.CorrelationIdHeader ?? CorrelationId)) 23 | { 24 | } 25 | 26 | /// 27 | /// Initializes a new instance of the class. 28 | /// 29 | /// The accessor used to retreive the correlation id. 30 | public CorrelationIdContextProvider(ICorrelationIdAccessor? accessor) => Accessor = accessor; 31 | 32 | /// 33 | /// Gets the accessor used to retreive the correlation id. 34 | /// 35 | public ICorrelationIdAccessor? Accessor { get; } 36 | 37 | /// 38 | /// Add custom context to the object. 39 | /// 40 | /// The log entry to add custom context to. 41 | public void AddContext(LogEntry logEntry) 42 | { 43 | #if NET6_0_OR_GREATER 44 | ArgumentNullException.ThrowIfNull(logEntry); 45 | #else 46 | if (logEntry is null) { throw new ArgumentNullException(nameof(logEntry)); } 47 | #endif 48 | logEntry.CorrelationId ??= Accessor?.CorrelationId; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /RockLib.Logging.AspNetCore/CorrelationIdContextProviderOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using static RockLib.DistributedTracing.AspNetCore.HeaderNames; 4 | 5 | namespace RockLib.Logging.AspNetCore; 6 | 7 | /// 8 | /// Defines options for . 9 | /// 10 | public class CorrelationIdContextProviderOptions 11 | { 12 | private string _correlationIdHeader = CorrelationId; 13 | 14 | /// 15 | /// The name of the correlation id header. 16 | /// 17 | public string CorrelationIdHeader 18 | { 19 | get => _correlationIdHeader; 20 | set 21 | { 22 | if (string.IsNullOrWhiteSpace(value)) 23 | { 24 | throw new ArgumentNullException(nameof(value)); 25 | } 26 | 27 | _correlationIdHeader = value; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /RockLib.Logging.AspNetCore/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /RockLib.Logging.AspNetCore/ForwardedForContextProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.Extensions.Primitives; 3 | 4 | namespace RockLib.Logging.AspNetCore; 5 | 6 | /// 7 | /// An implementation of used to add the ForwardedFor value to a . 8 | /// 9 | public class ForwardedForContextProvider : IContextProvider 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// The http context accessor used to retreive the ForwardedFor value. 15 | public ForwardedForContextProvider(IHttpContextAccessor httpContextAccessor) 16 | : this(httpContextAccessor?.HttpContext?.GetForwardedFor() ?? default) 17 | { 18 | } 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | /// The FowardedFor value. 24 | public ForwardedForContextProvider(StringValues forwardedFor) => ForwardedFor = forwardedFor; 25 | 26 | /// 27 | /// Gets the ForwardedFor value. 28 | /// 29 | public StringValues ForwardedFor { get; } 30 | 31 | /// 32 | /// Add custom context to the object. 33 | /// 34 | /// The log entry to add custom context to. 35 | public void AddContext(LogEntry logEntry) => logEntry.SetForwardedFor(ForwardedFor); 36 | } 37 | -------------------------------------------------------------------------------- /RockLib.Logging.AspNetCore/HeaderNames.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Logging.AspNetCore; 2 | 3 | /// 4 | /// A class for http header names. 5 | /// 6 | public static class HeaderNames 7 | { 8 | /// 9 | /// The forwarded for http header name. 10 | /// 11 | public const string ForwardedFor = "X-Forwarded-For"; 12 | } 13 | -------------------------------------------------------------------------------- /RockLib.Logging.AspNetCore/HttpContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Http.Features; 3 | using Microsoft.AspNetCore.Http.Headers; 4 | using Microsoft.AspNetCore.Routing; 5 | using Microsoft.Extensions.Primitives; 6 | using System; 7 | using System.Net; 8 | 9 | using static Microsoft.Net.Http.Headers.HeaderNames; 10 | 11 | namespace RockLib.Logging.AspNetCore; 12 | 13 | using static HeaderNames; 14 | 15 | /// 16 | /// Extensions for . 17 | /// 18 | public static class HttpContextExtensions 19 | { 20 | /// 21 | /// Gets the http request method from an . 22 | /// 23 | /// The http context. 24 | /// The http request method. 25 | public static string? GetMethod(this HttpContext httpContext) => 26 | httpContext?.Request?.Method; 27 | 28 | /// 29 | /// Gets the http request path from an . 30 | /// 31 | /// The http context. 32 | /// The http request path. 33 | public static string? GetPath(this HttpContext httpContext) 34 | { 35 | if (httpContext?.Features[typeof(IEndpointFeature)] is IEndpointFeature endpointFeature 36 | && endpointFeature.Endpoint is RouteEndpoint endpoint) 37 | { 38 | return endpoint.RoutePattern.RawText; 39 | } 40 | 41 | return httpContext?.Request?.Path; 42 | } 43 | 44 | /// 45 | /// Gets the http user agent header from an . 46 | /// 47 | /// The http context. 48 | /// The http user agent header. 49 | public static StringValues GetUserAgent(this HttpContext httpContext) => 50 | httpContext.GetHeaderValue(UserAgent); 51 | 52 | /// 53 | /// Gets the http referrer header from an . 54 | /// 55 | /// The http context. 56 | /// The http referrer header. 57 | public static Uri? GetReferrer(this HttpContext httpContext) => 58 | httpContext?.Request?.GetTypedHeaders() is RequestHeaders headers 59 | ? headers.Referer 60 | : null; 61 | 62 | /// 63 | /// Gets the connection remote ip address from an . 64 | /// 65 | /// The http context. 66 | /// The remote ip address. 67 | public static IPAddress? GetRemoteIpAddress(this HttpContext httpContext) => 68 | httpContext?.Connection?.RemoteIpAddress; 69 | 70 | /// 71 | /// Gets the forwarded for header from an . 72 | /// 73 | /// The http context. 74 | /// The forwarded for header. 75 | public static StringValues GetForwardedFor(this HttpContext httpContext) => 76 | httpContext.GetHeaderValue(ForwardedFor); 77 | 78 | private static StringValues GetHeaderValue(this HttpContext httpContext, string headerName) => 79 | httpContext?.Request?.Headers is IHeaderDictionary headers 80 | && headers.TryGetValue(headerName, out var headerValue) 81 | ? headerValue 82 | : default; 83 | } 84 | -------------------------------------------------------------------------------- /RockLib.Logging.AspNetCore/HttpContextProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.Extensions.Options; 3 | using System.Collections.Generic; 4 | 5 | namespace RockLib.Logging.AspNetCore; 6 | 7 | /// 8 | /// An implementation of used to add various http context values to a . 9 | /// 10 | public class HttpContextProvider : IContextProvider 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The http context accessor used to retreive the various context values. 16 | /// Options for the . 17 | public HttpContextProvider(IHttpContextAccessor httpContextAccessor, 18 | IOptionsMonitor? correlationIdOptions = null) 19 | : this(new IContextProvider[] 20 | { 21 | new RequestMethodContextProvider(httpContextAccessor), 22 | new PathContextProvider(httpContextAccessor), 23 | new UserAgentContextProvider(httpContextAccessor), 24 | new ReferrerContextProvider(httpContextAccessor), 25 | new RemoteIpAddressContextProvider(httpContextAccessor), 26 | new ForwardedForContextProvider(httpContextAccessor), 27 | new CorrelationIdContextProvider(httpContextAccessor, correlationIdOptions) 28 | }) 29 | { 30 | } 31 | 32 | /// 33 | /// Initializes a new instance of the class. 34 | /// 35 | /// The set of providers used to retreive the various context values. 36 | protected HttpContextProvider(IReadOnlyCollection contextProviders) => ContextProviders = contextProviders; 37 | 38 | /// 39 | /// Gets the set of providers used to retrieve the context values. 40 | /// 41 | public IReadOnlyCollection ContextProviders { get; } 42 | 43 | /// 44 | /// Add custom context to the object. 45 | /// 46 | /// The log entry to add custom context to. 47 | public void AddContext(LogEntry logEntry) 48 | { 49 | foreach (var contextProvider in ContextProviders) 50 | { 51 | contextProvider.AddContext(logEntry); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /RockLib.Logging.AspNetCore/InfoLogAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Logging.AspNetCore; 2 | 3 | /// 4 | /// An action filter that records an info log each time an action is executed. 5 | /// 6 | public sealed class InfoLogAttribute : LoggingActionFilterAttribute 7 | { 8 | /// 9 | /// Initializes a new instance of the class. 10 | /// 11 | /// 12 | /// The message format string. The action name is used as the {0} placeholder when 13 | /// formatting the message. 14 | /// 15 | /// The name of the logger. 16 | public InfoLogAttribute(string? messageFormat = DefaultMessageFormat, string? loggerName = Logger.DefaultName) 17 | : base(messageFormat, loggerName, LogLevel.Info) 18 | { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /RockLib.Logging.AspNetCore/LoggerBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using RockLib.Logging.DependencyInjection; 2 | 3 | namespace RockLib.Logging.AspNetCore; 4 | 5 | /// 6 | /// Extensions for . 7 | /// 8 | public static class LoggerBuilderExtensions 9 | { 10 | /// 11 | /// Adds the to the logger builder. 12 | /// 13 | /// The logger builder. 14 | /// The same logger builder. 15 | public static ILoggerBuilder AddHttpContextProvider(this ILoggerBuilder builder) => 16 | builder.AddContextProvider(); 17 | } 18 | -------------------------------------------------------------------------------- /RockLib.Logging.AspNetCore/PathContextProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | 3 | namespace RockLib.Logging.AspNetCore; 4 | 5 | /// 6 | /// An implementation of used to add the path value to a . 7 | /// 8 | public class PathContextProvider : IContextProvider 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// The http context accessor used to retreive the path value. 14 | public PathContextProvider(IHttpContextAccessor httpContextAccessor) 15 | : this(httpContextAccessor?.HttpContext?.GetPath()) 16 | { 17 | } 18 | 19 | /// 20 | /// Initializes a new instance of the class. 21 | /// 22 | /// The path value. 23 | public PathContextProvider(string? path) => Path = path; 24 | 25 | /// 26 | /// Gets the path value. 27 | /// 28 | public string? Path { get; } 29 | 30 | /// 31 | /// Add custom context to the object. 32 | /// 33 | /// The log entry to add custom context to. 34 | public void AddContext(LogEntry logEntry) => logEntry.SetPath(Path); 35 | } 36 | -------------------------------------------------------------------------------- /RockLib.Logging.AspNetCore/ReferrerContextProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using System; 3 | 4 | namespace RockLib.Logging.AspNetCore; 5 | 6 | /// 7 | /// An implementation of used to add the referrer value to a . 8 | /// 9 | public class ReferrerContextProvider : IContextProvider 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// The http context accessor used to retreive the referrer value. 15 | public ReferrerContextProvider(IHttpContextAccessor httpContextAccessor) 16 | : this(httpContextAccessor?.HttpContext?.GetReferrer()) 17 | { 18 | } 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | /// The referrer value. 24 | public ReferrerContextProvider(string? referrer) 25 | : this(referrer is null ? null : new Uri(referrer, UriKind.RelativeOrAbsolute)) 26 | { 27 | } 28 | 29 | /// 30 | /// Initializes a new instance of the class. 31 | /// 32 | /// The referrer value. 33 | public ReferrerContextProvider(Uri? referrer) => Referrer = referrer; 34 | 35 | /// 36 | /// Gets the referrer value. 37 | /// 38 | public Uri? Referrer { get; } 39 | 40 | /// 41 | /// Add custom context to the object. 42 | /// 43 | /// The log entry to add custom context to. 44 | public void AddContext(LogEntry logEntry) => logEntry.SetReferrer(Referrer); 45 | } 46 | -------------------------------------------------------------------------------- /RockLib.Logging.AspNetCore/RemoteIpAddressContextProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using System.Net; 3 | 4 | namespace RockLib.Logging.AspNetCore; 5 | 6 | /// 7 | /// An implementation of used to add the remote ip address value to a . 8 | /// 9 | public class RemoteIpAddressContextProvider : IContextProvider 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// The http context accessor used to retreive the remote ip address value. 15 | public RemoteIpAddressContextProvider(IHttpContextAccessor httpContextAccessor) 16 | : this(httpContextAccessor?.HttpContext?.GetRemoteIpAddress()) 17 | { 18 | } 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | /// The remote ip address value. 24 | public RemoteIpAddressContextProvider(string remoteIpAddress) 25 | : this(remoteIpAddress is null ? null : IPAddress.Parse(remoteIpAddress)) 26 | { 27 | } 28 | 29 | /// 30 | /// Initializes a new instance of the class. 31 | /// 32 | /// The remote ip address value. 33 | public RemoteIpAddressContextProvider(IPAddress? remoteIpAddress) => RemoteIpAddress = remoteIpAddress; 34 | 35 | /// 36 | /// Gets the remote ip address value. 37 | /// 38 | public IPAddress? RemoteIpAddress { get; } 39 | 40 | /// 41 | /// Add custom context to the object. 42 | /// 43 | /// The log entry to add custom context to. 44 | public void AddContext(LogEntry logEntry) => logEntry.SetRemoteIpAddress(RemoteIpAddress); 45 | } 46 | -------------------------------------------------------------------------------- /RockLib.Logging.AspNetCore/RequestMethodContextProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | 3 | namespace RockLib.Logging.AspNetCore; 4 | 5 | /// 6 | /// An implementation of used to add the request method value to a . 7 | /// 8 | public class RequestMethodContextProvider : IContextProvider 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// The http context accessor used to retreive the request method value. 14 | public RequestMethodContextProvider(IHttpContextAccessor httpContextAccessor) 15 | : this(httpContextAccessor?.HttpContext?.GetMethod()) 16 | { 17 | } 18 | 19 | /// 20 | /// Initializes a new instance of the class. 21 | /// 22 | /// The request method value. 23 | public RequestMethodContextProvider(string? requestMethod) => RequestMethod = requestMethod; 24 | 25 | /// 26 | /// Gets the request method value. 27 | /// 28 | public string? RequestMethod { get; } 29 | 30 | /// 31 | /// Add custom context to the object. 32 | /// 33 | /// The log entry to add custom context to. 34 | public void AddContext(LogEntry logEntry) => logEntry.SetRequestMethod(RequestMethod); 35 | } 36 | -------------------------------------------------------------------------------- /RockLib.Logging.AspNetCore/RockLib.Logging.AspNetCore.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Embedded 4 | RockLib.Logging for AspNetCore. Includes context providers and a logging action filter. 5 | True 6 | True 7 | NU1605,NU1608 8 | icon.png 9 | RockLib.Logging.AspNetCore 10 | LICENSE.md 11 | https://github.com/RockLib/RockLib.Logging 12 | false 13 | A changelog is available at https://github.com/RockLib/RockLib.Logging/blob/main/RockLib.Logging.AspNetCore/CHANGELOG.md. 14 | RockLib Logging Logger AspNetCore HttpContext ActionFilter 15 | 6.0.0 16 | True 17 | 6.0.0 18 | 19 | 20 | bin\$(Configuration)\$(TargetFramework)\$(PackageId).xml 21 | 22 | 23 | true 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /RockLib.Logging.AspNetCore/RockLib.Logging.AspNetCore.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27428.2037 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Logging.AspNetCore", "RockLib.Logging.AspNetCore.csproj", "{A07A8BEF-6786-44A5-8E2D-60A14FFBE772}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Logging.AspNetCore.Tests", "..\Tests\RockLib.Logging.AspNetCore.Tests\RockLib.Logging.AspNetCore.Tests.csproj", "{1E143C4D-C527-4038-8CD3-5F26EC7E644B}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {A07A8BEF-6786-44A5-8E2D-60A14FFBE772}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {A07A8BEF-6786-44A5-8E2D-60A14FFBE772}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {A07A8BEF-6786-44A5-8E2D-60A14FFBE772}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {A07A8BEF-6786-44A5-8E2D-60A14FFBE772}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {1E143C4D-C527-4038-8CD3-5F26EC7E644B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {1E143C4D-C527-4038-8CD3-5F26EC7E644B}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {1E143C4D-C527-4038-8CD3-5F26EC7E644B}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {1E143C4D-C527-4038-8CD3-5F26EC7E644B}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {806D89B7-78D1-4038-944B-AFFE3C7A0F00} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /RockLib.Logging.AspNetCore/RouteNotFoundMiddlewareExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | 3 | namespace RockLib.Logging.AspNetCore; 4 | 5 | /// 6 | /// Provides extension methods for the class. 7 | /// 8 | public static class RouteNotFoundMiddlewareExtensions 9 | { 10 | /// 11 | /// Adds to the application's request pipeline. 12 | /// 13 | /// The . 14 | /// The . 15 | public static IApplicationBuilder UseRouteNotFoundLogging(this IApplicationBuilder builder) => 16 | builder.UseMiddleware(); 17 | } 18 | -------------------------------------------------------------------------------- /RockLib.Logging.AspNetCore/RouteNotFoundMiddlewareOptions.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Logging.AspNetCore; 2 | 3 | /// 4 | /// Defines the options for the class. 5 | /// 6 | public class RouteNotFoundMiddlewareOptions 7 | { 8 | /// 9 | /// Gets or sets the name of the logger. 10 | /// 11 | public string? LoggerName { get; set; } 12 | 13 | /// 14 | /// Gets or sets the level used when sending logs. 15 | /// 16 | public LogLevel LogLevel { get; set; } 17 | 18 | /// 19 | /// Gets or sets the message used when sending logs. 20 | /// 21 | public string? LogMessage { get; set; } 22 | } -------------------------------------------------------------------------------- /RockLib.Logging.AspNetCore/UserAgentContextProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | 3 | namespace RockLib.Logging.AspNetCore; 4 | 5 | /// 6 | /// An implementation of used to add the UserAgent value to a . 7 | /// 8 | public class UserAgentContextProvider : IContextProvider 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// The http context accessor used to retreive the UserAgent value. 14 | public UserAgentContextProvider(IHttpContextAccessor httpContextAccessor) 15 | : this(httpContextAccessor?.HttpContext?.GetUserAgent()) 16 | { 17 | } 18 | 19 | /// 20 | /// Initializes a new instance of the class. 21 | /// 22 | /// The UserAgent value. 23 | public UserAgentContextProvider(string? userAgent) => UserAgent = userAgent; 24 | 25 | /// 26 | /// Gets the UserAgent value. 27 | /// 28 | public string? UserAgent { get; } 29 | 30 | /// 31 | /// Add custom context to the object. 32 | /// 33 | /// The log entry to add custom context to. 34 | public void AddContext(LogEntry logEntry) => logEntry.SetUserAgent(UserAgent); 35 | } 36 | -------------------------------------------------------------------------------- /RockLib.Logging.Microsoft.Extensions/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | 6 | [*.cs] 7 | # Styling 8 | indent_style = space 9 | indent_size = 4 10 | csharp_indent_case_contents = true 11 | csharp_indent_switch_labels = true 12 | csharp_new_line_before_catch = true 13 | csharp_new_line_before_else = true 14 | csharp_new_line_before_finally = true 15 | csharp_new_line_before_members_in_anonymous_types = false 16 | csharp_new_line_before_members_in_object_initializers = false 17 | csharp_new_line_before_open_brace = methods, control_blocks, types, properties, lambdas, accessors, object_collection_array_initializers 18 | csharp_new_line_between_query_expression_clauses = true 19 | csharp_prefer_braces = false:suggestion 20 | csharp_prefer_simple_default_expression = true:suggestion 21 | csharp_preferred_modifier_order = public,private,internal,protected,static,readonly,async,override,sealed:suggestion 22 | csharp_preserve_single_line_blocks = true 23 | csharp_preserve_single_line_statements = true 24 | csharp_space_after_cast = false 25 | csharp_space_after_colon_in_inheritance_clause = true 26 | csharp_space_after_keywords_in_control_flow_statements = true 27 | csharp_space_before_colon_in_inheritance_clause = true 28 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 29 | csharp_space_between_method_call_name_and_opening_parenthesis = false 30 | csharp_space_between_method_call_parameter_list_parentheses = false 31 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 32 | csharp_space_between_method_declaration_parameter_list_parentheses = false 33 | csharp_style_pattern_matching_over_as_with_null_check = true:error 34 | csharp_style_expression_bodied_accessors = true:error 35 | csharp_style_expression_bodied_constructors = true:error 36 | csharp_style_expression_bodied_methods = true:error 37 | csharp_style_expression_bodied_properties = true:error 38 | csharp_style_namespace_declarations = file_scoped:error 39 | csharp_style_inlined_variable_declaration = true:error 40 | csharp_style_var_elsewhere = true:error 41 | csharp_style_var_for_built_in_types = true:error 42 | csharp_style_var_when_type_is_apparent = true:error 43 | dotnet_sort_system_directives_first = false 44 | dotnet_style_explicit_tuple_names = true:suggestion 45 | dotnet_style_object_initializer = true:suggestion 46 | csharp_style_pattern_local_over_anonymous_function = false:error 47 | dotnet_style_predefined_type_for_member_access = true:error 48 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:error 49 | dotnet_style_prefer_inferred_tuple_names = true:error 50 | dotnet_style_predefined_type_for_locals_parameters_members = true:error 51 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:error 52 | dotnet_style_qualification_for_field = false:error 53 | dotnet_style_qualification_for_method = false:error 54 | dotnet_style_qualification_for_property = false:error 55 | 56 | # Analyzer Configuration 57 | # These are rules we want to either ignore or have set as suggestion or info 58 | 59 | # CA1014: Mark assemblies with CLSCompliant 60 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1014 61 | dotnet_diagnostic.CA1014.severity = none 62 | 63 | # CA1725: Parameter names should match base declaration 64 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1725 65 | dotnet_diagnostic.CA1725.severity = suggestion 66 | 67 | # CA2227: Collection properties should be read only 68 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2227 69 | dotnet_diagnostic.CA2227.severity = suggestion -------------------------------------------------------------------------------- /RockLib.Logging.Microsoft.Extensions/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # RockLib.Logging.Microsoft.Extensions Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## 4.0.0 - 2025-01-31 9 | 10 | #### Changed 11 | - Finalized 4.0.0 release. 12 | 13 | ## 4.0.0-alpha.1 - 2025-01-30 14 | 15 | #### Changed 16 | - Removed .NET 6 as a target framework 17 | - Updated the following package references 18 | - RockLib.Logging - 6.0.0-alpha.1 19 | 20 | ## 3.1.2 - 2024-10-22 21 | 22 | #### Changed 23 | - RockLib.Logging 5.1.1 -> RockLib.Logging 5.1.2. 24 | - Microsoft.Extensions.Logging 8.0.0 -> Microsoft.Extensions.Logging 8.0.1. 25 | 26 | ## 3.1.1 - 2024-07-16 27 | 28 | #### Changed 29 | - RockLib.Logging.5.1.0 -> RockLib.Logging.5.1.1. 30 | 31 | ## 3.1.0 - 2024-02-26 32 | 33 | #### Changed 34 | - Finalized 3.1.0 release. 35 | 36 | ## 3.0.0 - 2024-02-15 37 | 38 | #### Changed 39 | - Finalized 3.0.0 release. 40 | 41 | ## 3.0.0-alpha.1 - 2024-01-31 42 | 43 | #### Changed 44 | - Supported targets: net6.0, net8.0, and net48 - netcoreapp3.1 was removed. 45 | - Updated package references. 46 | 47 | ## 2.0.1 - 2023-02-21 48 | 49 | #### Changed 50 | - Updated RockLib.Logging package reference to `4.0.2` 51 | 52 | ## 2.0.0 - 2022-11-15 53 | 54 | #### Added 55 | - Added `.editorconfig` and `Directory.Build.props` files to ensure consistency. 56 | 57 | #### Changed 58 | - Supported targets: net6.0, netcoreapp3.1, and net48. 59 | - As the package now uses nullable reference types, some method parameters now specify if they can accept nullable values. 60 | 61 | ## 2.0.0-alpha.1 - 2022-11-10 62 | 63 | #### Added 64 | - Added `.editorconfig` and `Directory.Build.props` files to ensure consistency. 65 | 66 | #### Changed 67 | - Supported targets: net6.0, netcoreapp3.1, and net48. 68 | - As the package now uses nullable reference types, some method parameters now specify if they can accept nullable values. 69 | 70 | ## 1.0.5 - 2021-08-11 71 | 72 | #### Changed 73 | 74 | - Changes "Quicken Loans" to "Rocket Mortgage". 75 | - Updates RockLib.Logging to latest version, [3.0.8](https://github.com/RockLib/RockLib.Logging/blob/main/RockLib.Logging/CHANGELOG.md#308---2021-08-11). 76 | 77 | ## 1.0.4 - 2021-07-30 78 | 79 | #### Changed 80 | 81 | - Updates [RockLib.Logging](https://github.com/RockLib/RockLib.Logging/blob/main/RockLib.Logging/CHANGELOG.md#307---2021-07-30) package to latest version. 82 | 83 | ## 1.0.3 - 2021-07-27 84 | 85 | #### Changed 86 | 87 | - Updates [RockLib.Logging](https://github.com/RockLib/RockLib.Logging/blob/main/RockLib.Logging/CHANGELOG.md#306---2021-07-22) package to latest version. 88 | 89 | #### Added 90 | 91 | - Adds [RockLib.Logging.Microsoft.Extensions.Analyzers](https://github.com/RockLib/RockLib.Analyzers/blob/main/Logging.Microsoft.Extensions/CHANGELOG.md#100---2021-07-21) package reference. 92 | 93 | ## 1.0.2 - 2021-05-06 94 | 95 | #### Added 96 | 97 | - Adds SourceLink to nuget package. 98 | 99 | #### Changed 100 | 101 | - Updates RockLib.Logging package to latest version, which includes SourceLink. 102 | 103 | ---- 104 | 105 | **Note:** Release notes in the above format are not available for earlier versions of 106 | RockLib.Logging.Microsoft.Extensions. What follows below are the original release notes. 107 | 108 | ---- 109 | 110 | ## 1.0.1 - 2021-02-19 111 | 112 | Adds net5.0 target. 113 | 114 | ## 1.0.0 - 2020-11-16 115 | 116 | Initial release. 117 | 118 | ## 1.0.0-alpha02 - 2020-08-26 119 | 120 | Updates RockLib.Logging package to the latest version, 2.1.4. 121 | 122 | ## 1.0.0-alpha01 - 2020-08-24 123 | 124 | Initial prerelease. 125 | -------------------------------------------------------------------------------- /RockLib.Logging.Microsoft.Extensions/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /RockLib.Logging.Microsoft.Extensions/RockLib.Logging.Microsoft.Extensions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Embedded 4 | An implementation of Microsoft.Extensions.Logging that uses RockLib.Logging. 5 | True 6 | True 7 | icon.png 8 | RockLib.Logging.Microsoft.Extensions 9 | LICENSE.md 10 | https://github.com/RockLib/RockLib.Logging 11 | A changelog is available at https://github.com/RockLib/RockLib.Logging/blob/main/RockLib.Logging.Microsoft.Extensions/CHANGELOG.md. 12 | false 13 | RockLib Logging Logger Microsoft Extensions 14 | 4.0.0 15 | True 16 | RockLib.Logging 17 | 4.0.0 18 | 19 | 20 | bin\$(Configuration)\$(TargetFramework)\$(PackageId).xml 21 | 22 | 23 | true 24 | 25 | 26 | 27 | runtime; build; native; contentfiles; analyzers; buildtransitive 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /RockLib.Logging.Microsoft.Extensions/RockLib.Logging.Microsoft.Extensions.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32825.248 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Logging.Microsoft.Extensions", "RockLib.Logging.Microsoft.Extensions.csproj", "{16C1DBC6-BE39-4109-932F-EE33A601ADB6}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Logging.Microsoft.Extensions.Tests", "..\Tests\RockLib.Logging.Microsoft.Extensions.Tests\RockLib.Logging.Microsoft.Extensions.Tests.csproj", "{88065170-E5E2-4E4B-8E04-B6B68F862076}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DA85831C-723F-4E0F-AF50-8D6BDCEC6A61}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {16C1DBC6-BE39-4109-932F-EE33A601ADB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {16C1DBC6-BE39-4109-932F-EE33A601ADB6}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {16C1DBC6-BE39-4109-932F-EE33A601ADB6}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {16C1DBC6-BE39-4109-932F-EE33A601ADB6}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {88065170-E5E2-4E4B-8E04-B6B68F862076}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {88065170-E5E2-4E4B-8E04-B6B68F862076}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {88065170-E5E2-4E4B-8E04-B6B68F862076}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {88065170-E5E2-4E4B-8E04-B6B68F862076}.Release|Any CPU.Build.0 = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(SolutionProperties) = preSolution 28 | HideSolutionNode = FALSE 29 | EndGlobalSection 30 | GlobalSection(ExtensibilityGlobals) = postSolution 31 | SolutionGuid = {806D89B7-78D1-4038-944B-AFFE3C7A0F00} 32 | EndGlobalSection 33 | EndGlobal 34 | -------------------------------------------------------------------------------- /RockLib.Logging.Microsoft.Extensions/RockLibLoggerOptions.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Logging; 2 | 3 | /// 4 | /// Options for a . 5 | /// 6 | public class RockLibLoggerOptions 7 | { 8 | /// 9 | /// Gets or sets a value indicating whether to include scopes when logging. 10 | /// 11 | public bool IncludeScopes { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /RockLib.Logging.Microsoft.Extensions/Shims.cs: -------------------------------------------------------------------------------- 1 | #if NET48 2 | namespace System.Diagnostics.CodeAnalysis; 3 | 4 | /// Specifies that null is disallowed as an input even if the corresponding type allows it. 5 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] 6 | public sealed class DisallowNullAttribute : Attribute { } 7 | #endif -------------------------------------------------------------------------------- /RockLib.Logging.Moq/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | 6 | [*.cs] 7 | # Styling 8 | indent_style = space 9 | indent_size = 4 10 | csharp_indent_case_contents = true 11 | csharp_indent_switch_labels = true 12 | csharp_new_line_before_catch = true 13 | csharp_new_line_before_else = true 14 | csharp_new_line_before_finally = true 15 | csharp_new_line_before_members_in_anonymous_types = false 16 | csharp_new_line_before_members_in_object_initializers = false 17 | csharp_new_line_before_open_brace = methods, control_blocks, types, properties, lambdas, accessors, object_collection_array_initializers 18 | csharp_new_line_between_query_expression_clauses = true 19 | csharp_prefer_braces = false:suggestion 20 | csharp_prefer_simple_default_expression = true:suggestion 21 | csharp_preferred_modifier_order = public,private,internal,protected,static,readonly,async,override,sealed:suggestion 22 | csharp_preserve_single_line_blocks = true 23 | csharp_preserve_single_line_statements = true 24 | csharp_space_after_cast = false 25 | csharp_space_after_colon_in_inheritance_clause = true 26 | csharp_space_after_keywords_in_control_flow_statements = true 27 | csharp_space_before_colon_in_inheritance_clause = true 28 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 29 | csharp_space_between_method_call_name_and_opening_parenthesis = false 30 | csharp_space_between_method_call_parameter_list_parentheses = false 31 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 32 | csharp_space_between_method_declaration_parameter_list_parentheses = false 33 | csharp_style_pattern_matching_over_as_with_null_check = true:error 34 | csharp_style_expression_bodied_accessors = true:error 35 | csharp_style_expression_bodied_constructors = true:error 36 | csharp_style_expression_bodied_methods = true:error 37 | csharp_style_expression_bodied_properties = true:error 38 | csharp_style_namespace_declarations = file_scoped:error 39 | csharp_style_inlined_variable_declaration = true:error 40 | csharp_style_var_elsewhere = true:error 41 | csharp_style_var_for_built_in_types = true:error 42 | csharp_style_var_when_type_is_apparent = true:error 43 | dotnet_sort_system_directives_first = false 44 | dotnet_style_explicit_tuple_names = true:suggestion 45 | dotnet_style_object_initializer = true:suggestion 46 | csharp_style_pattern_local_over_anonymous_function = false:error 47 | dotnet_style_predefined_type_for_member_access = true:error 48 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:error 49 | dotnet_style_prefer_inferred_tuple_names = true:error 50 | dotnet_style_predefined_type_for_locals_parameters_members = true:error 51 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:error 52 | dotnet_style_qualification_for_field = false:error 53 | dotnet_style_qualification_for_method = false:error 54 | dotnet_style_qualification_for_property = false:error 55 | 56 | # Analyzer Configuration 57 | # These are rules we want to either ignore or have set as suggestion or info 58 | 59 | # CA1014: Mark assemblies with CLSCompliant 60 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1014 61 | dotnet_diagnostic.CA1014.severity = none 62 | 63 | # CA1725: Parameter names should match base declaration 64 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1725 65 | dotnet_diagnostic.CA1725.severity = suggestion 66 | 67 | # CA2227: Collection properties should be read only 68 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2227 69 | dotnet_diagnostic.CA2227.severity = suggestion -------------------------------------------------------------------------------- /RockLib.Logging.Moq/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /RockLib.Logging.Moq/MockLogger.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | 3 | namespace RockLib.Logging.Moq; 4 | 5 | /// 6 | /// Provides a mock implementation of . 7 | /// 8 | public class MockLogger : Mock 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// The level of the mock logger. 14 | /// The name of the mock logger. 15 | /// The behavior of the mock. 16 | public MockLogger(LogLevel level = LogLevel.Debug, string name = Logger.DefaultName, MockBehavior behavior = MockBehavior.Default) 17 | : base(behavior) => this.SetupLogger(level, name); 18 | } 19 | -------------------------------------------------------------------------------- /RockLib.Logging.Moq/RockLib.Logging.Moq.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Embedded 4 | Extensions for verifying logging operations with Moq. 5 | True 6 | True 7 | RockLib.Logging.Moq 8 | LICENSE.md 9 | https://github.com/RockLib/RockLib.Logging 10 | A changelog is available at https://github.com/RockLib/RockLib.Logging/blob/main/RockLib.Logging.Moq/CHANGELOG.md. 11 | false 12 | RockLib Logging Logger Extensions Moq 13 | 4.0.0 14 | icon.png 15 | True 16 | 4.0.0 17 | 18 | 19 | bin\$(Configuration)\$(TargetFramework)\$(PackageId).xml 20 | 21 | 22 | true 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /RockLib.Logging.Moq/RockLib.Logging.Moq.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29230.47 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Logging.Moq", "RockLib.Logging.Moq.csproj", "{1781C3A1-5CC5-4338-9D68-B86D7C4C7356}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Logging.Moq.Tests", "..\Tests\RockLib.Logging.Moq.Tests\RockLib.Logging.Moq.Tests.csproj", "{86EFB7D5-CF52-476D-BA9F-1D94778517C9}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {1781C3A1-5CC5-4338-9D68-B86D7C4C7356}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {1781C3A1-5CC5-4338-9D68-B86D7C4C7356}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {1781C3A1-5CC5-4338-9D68-B86D7C4C7356}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {1781C3A1-5CC5-4338-9D68-B86D7C4C7356}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {86EFB7D5-CF52-476D-BA9F-1D94778517C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {86EFB7D5-CF52-476D-BA9F-1D94778517C9}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {86EFB7D5-CF52-476D-BA9F-1D94778517C9}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {86EFB7D5-CF52-476D-BA9F-1D94778517C9}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {806D89B7-78D1-4038-944B-AFFE3C7A0F00} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /RockLib.Logging/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | 6 | [*.cs] 7 | # Styling 8 | indent_style = space 9 | indent_size = 4 10 | csharp_indent_case_contents = true 11 | csharp_indent_switch_labels = true 12 | csharp_new_line_before_catch = true 13 | csharp_new_line_before_else = true 14 | csharp_new_line_before_finally = true 15 | csharp_new_line_before_members_in_anonymous_types = false 16 | csharp_new_line_before_members_in_object_initializers = false 17 | csharp_new_line_before_open_brace = methods, control_blocks, types, properties, lambdas, accessors, object_collection_array_initializers 18 | csharp_new_line_between_query_expression_clauses = true 19 | csharp_prefer_braces = false:suggestion 20 | csharp_prefer_simple_default_expression = true:suggestion 21 | csharp_preferred_modifier_order = public,private,internal,protected,static,readonly,async,override,sealed:suggestion 22 | csharp_preserve_single_line_blocks = true 23 | csharp_preserve_single_line_statements = true 24 | csharp_space_after_cast = false 25 | csharp_space_after_colon_in_inheritance_clause = true 26 | csharp_space_after_keywords_in_control_flow_statements = true 27 | csharp_space_before_colon_in_inheritance_clause = true 28 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 29 | csharp_space_between_method_call_name_and_opening_parenthesis = false 30 | csharp_space_between_method_call_parameter_list_parentheses = false 31 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 32 | csharp_space_between_method_declaration_parameter_list_parentheses = false 33 | csharp_style_pattern_matching_over_as_with_null_check = true:error 34 | csharp_style_expression_bodied_accessors = true:error 35 | csharp_style_expression_bodied_constructors = true:error 36 | csharp_style_expression_bodied_methods = true:error 37 | csharp_style_expression_bodied_properties = true:error 38 | csharp_style_namespace_declarations = file_scoped:error 39 | csharp_style_inlined_variable_declaration = true:error 40 | csharp_style_var_elsewhere = true:error 41 | csharp_style_var_for_built_in_types = true:error 42 | csharp_style_var_when_type_is_apparent = true:error 43 | dotnet_sort_system_directives_first = false 44 | dotnet_style_explicit_tuple_names = true:suggestion 45 | dotnet_style_object_initializer = true:suggestion 46 | csharp_style_pattern_local_over_anonymous_function = false:error 47 | dotnet_style_predefined_type_for_member_access = true:error 48 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:error 49 | dotnet_style_prefer_inferred_tuple_names = true:error 50 | dotnet_style_predefined_type_for_locals_parameters_members = true:error 51 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:error 52 | dotnet_style_qualification_for_field = false:error 53 | dotnet_style_qualification_for_method = false:error 54 | dotnet_style_qualification_for_property = false:error 55 | 56 | # Analyzer Configuration 57 | # These are rules we want to either ignore or have set as suggestion or info 58 | 59 | # CA1014: Mark assemblies with CLSCompliant 60 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1014 61 | dotnet_diagnostic.CA1014.severity = none 62 | 63 | # CA1725: Parameter names should match base declaration 64 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1725 65 | dotnet_diagnostic.CA1725.severity = suggestion 66 | 67 | # CA2227: Collection properties should be read only 68 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2227 69 | dotnet_diagnostic.CA2227.severity = suggestion 70 | -------------------------------------------------------------------------------- /RockLib.Logging/AssemblyAttributes.cs: -------------------------------------------------------------------------------- 1 | using RockLib.Configuration.ObjectFactory; 2 | using RockLib.Logging; 3 | using System.Collections.Generic; 4 | 5 | [assembly: ConfigSection("RockLib.Logging", typeof(List))] 6 | 7 | #if NET48 8 | namespace System.Diagnostics.CodeAnalysis; 9 | 10 | /// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. 11 | [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] 12 | public sealed class NotNullWhenAttribute : Attribute 13 | { 14 | /// Initializes the attribute with the specified return value condition. 15 | /// 16 | /// The return value condition. If the method returns this value, the associated parameter will not be null. 17 | /// 18 | public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; 19 | 20 | /// Gets the return value condition. 21 | public bool ReturnValue { get; } 22 | } 23 | #endif -------------------------------------------------------------------------------- /RockLib.Logging/Cached.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Net.NetworkInformation; 4 | 5 | namespace RockLib.Logging; 6 | 7 | internal static class Cached 8 | { 9 | private static readonly Lazy _ipAddress = new(GetMachineIpAddress); 10 | private static readonly Lazy _machineName = new(() => Environment.MachineName); 11 | private static readonly Lazy _userName = new(() => Environment.UserName); 12 | 13 | public static string? IpAddress => _ipAddress.Value; 14 | 15 | public static string MachineName => _machineName.Value; 16 | 17 | public static string UserName => _userName.Value; 18 | 19 | private static string? GetMachineIpAddress() 20 | { 21 | try 22 | { 23 | var ipAddresses = 24 | from i in NetworkInterface.GetAllNetworkInterfaces() 25 | where IsValidNetworkInterface(i) 26 | let p = i.GetIPProperties() 27 | where p.GatewayAddresses.Count > 0 28 | from a in p.UnicastAddresses 29 | where IsDuplicateAddressDetectionStatePreferred(a) && IsDnsEligible(a) 30 | select a.Address.ToString(); 31 | return string.Join("\n", ipAddresses); 32 | } 33 | #pragma warning disable CA1031 // Do not catch general exception types 34 | catch 35 | #pragma warning restore CA1031 // Do not catch general exception types 36 | { 37 | return null; 38 | } 39 | } 40 | 41 | private static bool IsValidNetworkInterface(NetworkInterface networkInterface) => networkInterface.OperationalStatus == OperationalStatus.Up 42 | && (networkInterface.NetworkInterfaceType == NetworkInterfaceType.Ethernet || networkInterface.NetworkInterfaceType == NetworkInterfaceType.Wireless80211); 43 | 44 | #pragma warning disable CA1416 // Validate platform compatibility 45 | private static bool IsDuplicateAddressDetectionStatePreferred(UnicastIPAddressInformation addressInfo) => 46 | addressInfo.DuplicateAddressDetectionState == DuplicateAddressDetectionState.Preferred; 47 | 48 | private static bool IsDnsEligible(UnicastIPAddressInformation addressInfo) => addressInfo.IsDnsEligible; 49 | #pragma warning restore CA1416 // Validate platform compatibility 50 | } 51 | -------------------------------------------------------------------------------- /RockLib.Logging/DependencyInjection/ILoggerBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Logging.DependencyInjection; 4 | 5 | /// 6 | /// A builder used to add log providers and context providers to a logger. 7 | /// 8 | public interface ILoggerBuilder 9 | { 10 | /// 11 | /// The name of the logger to build. 12 | /// 13 | string LoggerName { get; } 14 | 15 | /// 16 | /// Adds an to the logger. 17 | /// 18 | /// A method that creates the . 19 | /// The same . 20 | ILoggerBuilder AddLogProvider(Func logProviderRegistration); 21 | 22 | /// 23 | /// Adds an to the logger. 24 | /// 25 | /// A method that creates the . 26 | /// The same . 27 | ILoggerBuilder AddContextProvider(Func contextProviderRegistration); 28 | } -------------------------------------------------------------------------------- /RockLib.Logging/DependencyInjection/LoggerLookup.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Logging.DependencyInjection; 2 | 3 | /// 4 | /// Represents a method that retrieves an by its name. 5 | /// 6 | /// The name of the to retrieve. 7 | /// The matching . 8 | public delegate ILogger LoggerLookup(string name); -------------------------------------------------------------------------------- /RockLib.Logging/DependencyInjection/Options/ConsoleLogProviderOptions.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Logging.DependencyInjection; 2 | 3 | /// 4 | /// Defines an options class for creating a . 5 | /// 6 | public class ConsoleLogProviderOptions : FormattableLogProviderOptions 7 | { 8 | /// 9 | /// The type of console output stream to write to. 10 | /// 11 | public ConsoleLogProvider.Output Output { get; set; } 12 | } -------------------------------------------------------------------------------- /RockLib.Logging/DependencyInjection/Options/DebugLogProviderOptions.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Logging.DependencyInjection; 2 | 3 | /// 4 | /// Defines an options class for creating a . 5 | /// 6 | public class DebugLogProviderOptions : FormattableLogProviderOptions 7 | { 8 | } -------------------------------------------------------------------------------- /RockLib.Logging/DependencyInjection/Options/FileLogProviderOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Logging.DependencyInjection; 4 | 5 | /// 6 | /// Defines an options class for creating a . 7 | /// 8 | public class FileLogProviderOptions : FormattableLogProviderOptions 9 | { 10 | private string _file = string.Empty; 11 | 12 | /// 13 | /// The file to write to. 14 | /// 15 | public string File 16 | { 17 | get => _file; 18 | set => _file = value ?? throw new ArgumentNullException(nameof(value)); 19 | } 20 | } -------------------------------------------------------------------------------- /RockLib.Logging/DependencyInjection/Options/FormattableLogProviderOptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | 4 | namespace RockLib.Logging.DependencyInjection; 5 | 6 | /// 7 | /// Defines an options class for creating an that is 8 | /// formatted by an . 9 | /// 10 | public abstract class FormattableLogProviderOptions : LogProviderOptions 11 | { 12 | /// 13 | /// The method used to create the of the log provider. 14 | /// 15 | public Func? FormatterRegistration { get; set; } 16 | 17 | /// 18 | /// Sets to a method that creates a 19 | /// with the specified template. 20 | /// 21 | /// The template to use when formatting logs. 22 | public void SetTemplate(string template) 23 | { 24 | #if NET6_0_OR_GREATER 25 | ArgumentNullException.ThrowIfNull(template); 26 | #else 27 | if (template is null) 28 | { 29 | throw new ArgumentNullException(nameof(template)); 30 | } 31 | #endif 32 | 33 | SetFormatter(template); 34 | } 35 | 36 | /// 37 | /// Sets to a method that returns the specified 38 | /// . 39 | /// 40 | /// The to use for formatting logs. 41 | public void SetFormatter(ILogFormatter formatter) 42 | { 43 | #if NET6_0_OR_GREATER 44 | ArgumentNullException.ThrowIfNull(formatter); 45 | #else 46 | if (formatter is null) 47 | { 48 | throw new ArgumentNullException(nameof(formatter)); 49 | } 50 | #endif 51 | 52 | FormatterRegistration = serviceProvider => formatter; 53 | } 54 | 55 | /// 56 | /// Sets to a method that returns a new instance 57 | /// of type . 58 | /// 59 | /// 60 | /// The type of that the log provider uses for formatting logs. 61 | /// 62 | /// 63 | /// Constructor arguments for type that are not provided 64 | /// by the . 65 | /// 66 | public void SetFormatter(params object[] logFormatterParameters) 67 | where TLogFormatter : ILogFormatter => FormatterRegistration = serviceProvider => ActivatorUtilities.CreateInstance(serviceProvider, logFormatterParameters); 68 | } -------------------------------------------------------------------------------- /RockLib.Logging/DependencyInjection/Options/ILoggerOptions.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Logging.DependencyInjection; 2 | 3 | /// 4 | /// Defines an interface for the options for creating an . 5 | /// 6 | public interface ILoggerOptions 7 | { 8 | /// 9 | /// The logging level of the logger. 10 | /// 11 | LogLevel? Level { get; set; } 12 | 13 | /// 14 | /// Whether the logger should be disabled. 15 | /// 16 | bool? IsDisabled { get; set; } 17 | 18 | /// 19 | /// Whether to create a logger that automatically reloads itself when its configuration or options change. 20 | /// 21 | bool ReloadOnChange { get; set; } 22 | } -------------------------------------------------------------------------------- /RockLib.Logging/DependencyInjection/Options/LogProviderOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Logging.DependencyInjection; 4 | 5 | /// 6 | /// Defines an options class for creating an . 7 | /// 8 | public abstract class LogProviderOptions 9 | { 10 | /// 11 | /// The logging level of the log provider. 12 | /// 13 | public LogLevel Level { get; set; } 14 | 15 | /// 16 | /// The timeout of the log provider. 17 | /// 18 | public TimeSpan? Timeout { get; set; } 19 | 20 | /// 21 | /// Whether to create a log provider that automatically reloads itself when its options change. 22 | /// 23 | public bool ReloadOnChange { get; set; } 24 | } -------------------------------------------------------------------------------- /RockLib.Logging/DependencyInjection/Options/LoggerOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RockLib.Logging.DependencyInjection; 5 | 6 | /// 7 | /// Defines the options for creating an . 8 | /// 9 | public class LoggerOptions : ILoggerOptions 10 | { 11 | /// 12 | /// The list of log provider registrations that create the log providers of the logger. 13 | /// 14 | public IList> LogProviderRegistrations { get; } = new List>(); 15 | 16 | /// 17 | /// The list of context provider registrations that create the context providers of the logger. 18 | /// 19 | public IList> ContextProviderRegistrations { get; } = new List>(); 20 | 21 | /// 22 | /// The logging level of the logger. 23 | /// 24 | public LogLevel? Level { get; set; } 25 | 26 | /// 27 | /// Whether the logger should be disabled. 28 | /// 29 | public bool? IsDisabled { get; set; } 30 | 31 | /// 32 | /// Whether to create a logger that automatically reloads itself when its configuration or options change. 33 | /// 34 | public bool ReloadOnChange { get; set; } 35 | } -------------------------------------------------------------------------------- /RockLib.Logging/DependencyInjection/Options/RollingFileLogProviderOptions.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Logging.DependencyInjection; 2 | 3 | /// 4 | /// Defines an options class for creating a . 5 | /// 6 | public class RollingFileLogProviderOptions : FileLogProviderOptions 7 | { 8 | /// 9 | /// The maximum file size, in bytes, of the file. If the file size is greater than this value, 10 | /// it is archived. 11 | /// 12 | public int MaxFileSizeKilobytes { get; set; } = RollingFileLogProvider.DefaultMaxFileSizeKilobytes; 13 | 14 | /// 15 | /// The maximum number of archive files that will be kept. If the number of archive files is 16 | /// greater than this value, then they are deleted, oldest first. 17 | /// 18 | public int MaxArchiveCount { get; set; } = RollingFileLogProvider.DefaultMaxArchiveCount; 19 | 20 | /// 21 | /// The rollover period, indicating if/how the file should archived on a periodic basis. 22 | /// 23 | public RolloverPeriod RolloverPeriod { get; set; } = RollingFileLogProvider.DefaultRolloverPeriod; 24 | } -------------------------------------------------------------------------------- /RockLib.Logging/DependencyInjection/ReloadingLogProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using System; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using static RockLib.Logging.DependencyInjection.ServiceCollectionExtensions; 6 | 7 | namespace RockLib.Logging.DependencyInjection; 8 | 9 | internal sealed class ReloadingLogProvider : ILogProvider 10 | { 11 | private readonly Func _createLogProvider; 12 | private readonly string _name; 13 | private readonly Action _configureOptions; 14 | private ILogProvider _logProvider; 15 | private readonly IDisposable _changeListener; 16 | 17 | public ReloadingLogProvider(IOptionsMonitor optionsMonitor, TOptions options, 18 | Func createLogProvider, string name, Action configureOptions) 19 | { 20 | _createLogProvider = createLogProvider; 21 | _name = name; 22 | _configureOptions = configureOptions; 23 | _logProvider = _createLogProvider(options); 24 | 25 | _changeListener = optionsMonitor.OnChange(OptionsMonitorChanged)!; 26 | } 27 | 28 | public TimeSpan Timeout => _logProvider.Timeout; 29 | 30 | public LogLevel Level => _logProvider.Level; 31 | 32 | public Task WriteAsync(LogEntry logEntry, CancellationToken cancellationToken) => 33 | _logProvider.WriteAsync(logEntry, cancellationToken); 34 | 35 | private void OptionsMonitorChanged(TOptions options, string? name) 36 | { 37 | if (NamesEqual(_name, name)) 38 | { 39 | _configureOptions?.Invoke(options); 40 | _logProvider = _createLogProvider(options); 41 | } 42 | } 43 | 44 | ~ReloadingLogProvider() 45 | { 46 | _changeListener.Dispose(); 47 | } 48 | } -------------------------------------------------------------------------------- /RockLib.Logging/DependencyInjection/ReloadingLogger.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using RockLib.Logging.LogProcessing; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Runtime.CompilerServices; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using static RockLib.Logging.DependencyInjection.ServiceCollectionExtensions; 8 | using RockLib.Logging.LogProviders; 9 | 10 | namespace RockLib.Logging.DependencyInjection; 11 | 12 | internal sealed class ReloadingLogger : ILogger 13 | { 14 | private readonly IServiceProvider _serviceProvider; 15 | private readonly ILogProcessor _logProcessor; 16 | private readonly IReadOnlyCollection _logProviders; 17 | private readonly IReadOnlyCollection _contextProviders; 18 | private readonly Action _configureOptions; 19 | private Logger _logger; 20 | private readonly IDisposable _changeListener; 21 | 22 | public ReloadingLogger(IServiceProvider serviceProvider, 23 | ILogProcessor logProcessor, 24 | string name, 25 | IReadOnlyCollection logProviders, 26 | IReadOnlyCollection contextProviders, 27 | IOptionsMonitor optionsMonitor, 28 | LoggerOptions options, 29 | Action configureOptions) 30 | { 31 | Name = name; 32 | _serviceProvider = serviceProvider; 33 | _logProcessor = logProcessor; 34 | _logProviders = logProviders; 35 | _contextProviders = contextProviders; 36 | _configureOptions = configureOptions; 37 | _logger = CreateLogger(_serviceProvider, options); 38 | _changeListener = optionsMonitor.OnChange(OptionsMonitorChanged)!; 39 | } 40 | 41 | public bool IsDisabled => _logger.IsDisabled; 42 | 43 | public IErrorHandler? ErrorHandler { get => _logger.ErrorHandler; set => _logger.ErrorHandler = value; } 44 | 45 | public string Name { get; } 46 | 47 | public LogLevel Level => _logger.Level; 48 | 49 | public IReadOnlyCollection LogProviders => _logger.LogProviders; 50 | 51 | public IReadOnlyCollection ContextProviders => _logger.ContextProviders; 52 | 53 | public void Dispose() 54 | { 55 | _changeListener.Dispose(); 56 | _logger.Dispose(); 57 | } 58 | 59 | public void Log(LogEntry logEntry, [CallerMemberName] string? callerMemberName = null, [CallerFilePath] string? callerFilePath = null, [CallerLineNumber] int callerLineNumber = 0) => 60 | _logger.Log(logEntry, callerMemberName, callerFilePath, callerLineNumber); 61 | 62 | private void OptionsMonitorChanged(LoggerOptions options, string? name) 63 | { 64 | if (NamesEqual(Name, name)) 65 | { 66 | _configureOptions?.Invoke(options); 67 | 68 | var oldLogger = _logger; 69 | var newLogger = CreateLogger(_serviceProvider, options); 70 | 71 | if (oldLogger.ErrorHandler is not null && newLogger.ErrorHandler is null) 72 | { 73 | newLogger.ErrorHandler = oldLogger.ErrorHandler; 74 | } 75 | 76 | _logger = newLogger; 77 | } 78 | } 79 | 80 | private Logger CreateLogger(IServiceProvider serviceProvider, LoggerOptions options) => 81 | new(_logProcessor, Name, options.Level.GetValueOrDefault(), serviceProvider.GetService()!, 82 | _logProviders, options.IsDisabled.GetValueOrDefault(), _contextProviders); 83 | } -------------------------------------------------------------------------------- /RockLib.Logging/Diagnostics/LoggingTraceListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace RockLib.Logging.Diagnostics; 5 | 6 | using static Logger; 7 | 8 | /// 9 | /// A trace listener that records trace messages with an . 10 | /// 11 | public class LoggingTraceListener : TraceListener 12 | { 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | /// The logger that will record trace messages. 17 | /// The level that trace messages will be logged at. 18 | public LoggingTraceListener(ILogger logger, LogLevel? logLevel = null) 19 | : base(logger?.Name ?? DefaultName) 20 | { 21 | Logger = logger ?? throw new ArgumentNullException(nameof(logger)); 22 | LogLevel = logLevel ?? logger.Level; 23 | } 24 | 25 | /// 26 | /// Initializes a new instance of the class. 27 | /// 28 | /// 29 | /// The name of the logger to create, using , that will record 30 | /// trace messages. 31 | /// 32 | /// The level that trace messages will be logged at. 33 | public LoggingTraceListener(string loggerName = DefaultName, LogLevel? logLevel = null) 34 | : this(LoggerFactory.Create(loggerName ?? DefaultName), logLevel) 35 | { 36 | } 37 | 38 | /// 39 | /// The logger that records trace messages. 40 | /// 41 | public ILogger Logger { get; } 42 | 43 | /// 44 | /// The level that trace messages are logged at. 45 | /// 46 | public LogLevel LogLevel { get; } 47 | 48 | /// 49 | /// Writes the specified message to . 50 | /// 51 | /// The message to write. 52 | public override void Write(string? message) => WriteLog(message); 53 | 54 | /// 55 | /// Writes the specified message to . 56 | /// 57 | /// The message to write. 58 | public override void WriteLine(string? message) => WriteLog(message); 59 | 60 | private void WriteLog(string? message) 61 | { 62 | if (Logger.IsEnabled(LogLevel)) 63 | { 64 | Logger.Log(new LogEntry(message, LogLevel)); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /RockLib.Logging/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /RockLib.Logging/Error.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Logging; 4 | 5 | /// 6 | /// Defines an error to be handled by the interface. 7 | /// 8 | #pragma warning disable CA1716 // Identifiers should not match keywords 9 | public sealed class Error 10 | #pragma warning restore CA1716 // Identifiers should not match keywords 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// A message the describes the error. 16 | /// The exception responsible for the error, or null to indicate a timeout. 17 | /// 18 | /// The log provider that failed to write the log entry. 19 | /// 20 | /// The log entry that failed to write. 21 | /// 22 | /// The number of times the log provider has failed to write the log entry. 23 | /// 24 | public Error(string message, Exception? exception, ILogProvider logProvider, LogEntry logEntry, int failureCount) 25 | { 26 | if (failureCount < 0) 27 | { 28 | throw new ArgumentException("Cannot be less than zero.", nameof(failureCount)); 29 | } 30 | 31 | Message = message ?? throw new ArgumentNullException(nameof(message)); 32 | Exception = exception; 33 | LogProvider = logProvider ?? throw new ArgumentNullException(nameof(logProvider)); 34 | LogEntry = logEntry ?? throw new ArgumentNullException(nameof(logEntry)); 35 | FailureCount = failureCount; 36 | } 37 | 38 | /// 39 | /// Gets the message that describes the error. 40 | /// 41 | public string Message { get; } 42 | 43 | /// 44 | /// Gets the exception responsible for the error. 45 | /// 46 | public Exception? Exception { get; } 47 | 48 | /// 49 | /// Gets the log provider that failed to write the log entry. 50 | /// 51 | public ILogProvider LogProvider { get; } 52 | 53 | /// 54 | /// Gets the log entry that failed to write. 55 | /// 56 | public LogEntry LogEntry { get; } 57 | 58 | /// 59 | /// Gets the number of times the log provider has failed to write the log entry. 60 | /// 61 | public int FailureCount { get; } 62 | 63 | /// 64 | /// Gets a value indicating whether the error was a result of timing out. 65 | /// 66 | public bool IsTimeout => Exception is null; 67 | 68 | /// 69 | /// Gets the time that the error event occurred. 70 | /// 71 | public DateTime Timestamp { get; } = DateTime.UtcNow; 72 | 73 | /// 74 | /// Gets or sets a value indicating whether the log provider should attempt to send 75 | /// the log entry again. 76 | /// 77 | public bool ShouldRetry { get; set; } 78 | } -------------------------------------------------------------------------------- /RockLib.Logging/Extensions/EnabledExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Logging; 4 | 5 | /// 6 | /// Defines extension method that determine if a given logging level is enabled. 7 | /// 8 | public static class EnabledExtensions 9 | { 10 | /// 11 | /// Returns whether debug logging is enabled for the logger. 12 | /// 13 | /// The logger to check. 14 | /// True if debug logging is enabled, otherwise false. 15 | public static bool IsDebugEnabled(this ILogger logger) => logger.IsEnabled(LogLevel.Debug); 16 | 17 | /// 18 | /// Returns whether info logging is enabled for the logger. 19 | /// 20 | /// The logger to check. 21 | /// True if info logging is enabled, otherwise false. 22 | public static bool IsInfoEnabled(this ILogger logger) => logger.IsEnabled(LogLevel.Info); 23 | 24 | /// 25 | /// Returns whether warn logging is enabled for the logger. 26 | /// 27 | /// The logger to check. 28 | /// True if warn logging is enabled, otherwise false. 29 | public static bool IsWarnEnabled(this ILogger logger) => logger.IsEnabled(LogLevel.Warn); 30 | 31 | /// 32 | /// Returns whether error logging is enabled for the logger. 33 | /// 34 | /// The logger to check. 35 | /// True if error logging is enabled, otherwise false. 36 | public static bool IsErrorEnabled(this ILogger logger) => logger.IsEnabled(LogLevel.Error); 37 | 38 | /// 39 | /// Returns whether fatal logging is enabled for the logger. 40 | /// 41 | /// The logger to check. 42 | /// True if fatal logging is enabled, otherwise false. 43 | public static bool IsFatalEnabled(this ILogger logger) => logger.IsEnabled(LogLevel.Fatal); 44 | 45 | /// 46 | /// Returns whether audit logging is enabled for the logger. 47 | /// 48 | /// The logger to check. 49 | /// True if audit logging is enabled, otherwise false. 50 | public static bool IsAuditEnabled(this ILogger logger) => logger.IsEnabled(LogLevel.Audit); 51 | 52 | /// 53 | /// Returns whether logging is enabled for the given level for the logger. 54 | /// 55 | /// The logger to check. 56 | /// The level to check. 57 | /// True if logging is enabled for the given level, otherwise false. 58 | /// Thrown if is null 59 | public static bool IsEnabled(this ILogger logger, LogLevel level) 60 | { 61 | #if NET6_0_OR_GREATER 62 | ArgumentNullException.ThrowIfNull(logger); 63 | #else 64 | if(logger is null) { throw new ArgumentNullException(nameof(logger)); } 65 | #endif 66 | 67 | return !logger.IsDisabled && level >= logger.Level; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /RockLib.Logging/Extensions/ErrorHandlerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Logging; 4 | 5 | /// 6 | /// Defines extension methods for the interface for setting 7 | /// its property. 8 | /// 9 | public static class ErrorHandlerExtensions 10 | { 11 | /// 12 | /// Sets the property to an implementation of 13 | /// the interface that invokes the 14 | /// delegate when its method is called. 15 | /// 16 | /// 17 | /// The logger whose property is to be set. 18 | /// 19 | /// 20 | /// The delegate to be invoked when the method 21 | /// of the logger's property is called. 22 | /// 23 | /// Thrown if is null 24 | public static void SetErrorHandler(this ILogger logger, Action errorHandler) 25 | { 26 | #if NET6_0_OR_GREATER 27 | ArgumentNullException.ThrowIfNull(logger); 28 | #else 29 | if (logger is null) { throw new ArgumentNullException(nameof(logger)); } 30 | #endif 31 | logger.ErrorHandler = new DelegateErrorHandler(errorHandler); 32 | } 33 | 34 | private sealed class DelegateErrorHandler : IErrorHandler 35 | { 36 | private readonly Action _handleError; 37 | public DelegateErrorHandler(Action handleError) => _handleError = handleError; 38 | public void HandleError(Error error) => _handleError(error); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /RockLib.Logging/FormatToString/BlockIndentExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Logging; 4 | 5 | internal static class BlockIndentExtension 6 | { 7 | public static string BlockIndent(this string self, string indention) => 8 | #if NETCOREAPP3_1_OR_GREATER 9 | $"{indention}{self.Replace("\n", $"\n{indention}", StringComparison.CurrentCulture)}"; 10 | #else 11 | $"{indention}{self.Replace("\n", $"\n{indention}")}"; 12 | #endif 13 | } 14 | -------------------------------------------------------------------------------- /RockLib.Logging/IContextProvider.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Logging; 2 | 3 | /// 4 | /// Defines an object that adds custom context to objects. 5 | /// 6 | public interface IContextProvider 7 | { 8 | /// 9 | /// Add custom context to the object. 10 | /// 11 | /// The log entry to add custom context to. 12 | void AddContext(LogEntry logEntry); 13 | } 14 | -------------------------------------------------------------------------------- /RockLib.Logging/IErrorHandler.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Logging; 2 | 3 | /// 4 | /// Defines an object that can handle errors that occur when processing log entries. 5 | /// 6 | public interface IErrorHandler 7 | { 8 | /// 9 | /// Handle the specified error. If an implementation sets the 10 | /// property to true, then the log provider should 11 | /// attempt to send the log entry again. 12 | /// 13 | /// An error that has occurred. 14 | #pragma warning disable CA1716 // Identifiers should not match keywords 15 | void HandleError(Error error); 16 | #pragma warning restore CA1716 // Identifiers should not match keywords 17 | } -------------------------------------------------------------------------------- /RockLib.Logging/ILogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace RockLib.Logging; 6 | 7 | /// 8 | /// Defines an object used for logging. 9 | /// 10 | public interface ILogger : IDisposable 11 | { 12 | /// 13 | /// Gets the name of the logger. 14 | /// 15 | string Name { get; } 16 | 17 | /// 18 | /// Gets a value indicating whether the logger is disabled. 19 | /// 20 | bool IsDisabled { get; } 21 | 22 | /// 23 | /// Gets the logging level of the logger. 24 | /// 25 | /// 26 | /// Log entries with a level lower than the value of this property should 27 | /// not be logged by this logger. 28 | /// 29 | LogLevel Level { get; } 30 | 31 | /// 32 | /// Gets the collection of objects used by this logger. 33 | /// 34 | IReadOnlyCollection LogProviders { get; } 35 | 36 | /// 37 | /// Gets the collection of objects used by this logger. 38 | /// 39 | IReadOnlyCollection ContextProviders { get; } 40 | 41 | /// 42 | /// Gets or sets the object that handles errors that occur during log processing. 43 | /// 44 | IErrorHandler? ErrorHandler { get; set; } 45 | 46 | /// 47 | /// Logs the specified log entry. 48 | /// 49 | /// The log entry to log. 50 | /// The method or property name of the caller. 51 | /// The path of the source file that contains the caller. 52 | /// The line number in the source file at which this method is called. 53 | void Log( 54 | LogEntry logEntry, 55 | [CallerMemberName] string? callerMemberName = null, 56 | [CallerFilePath] string? callerFilePath = null, 57 | [CallerLineNumber] int callerLineNumber = 0); 58 | } -------------------------------------------------------------------------------- /RockLib.Logging/LogLevel.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Logging; 2 | 3 | /// 4 | /// Defines various logging levels. 5 | /// 6 | public enum LogLevel 7 | { 8 | /// 9 | /// The logging level has not been set. 10 | /// 11 | NotSet, 12 | 13 | /// 14 | /// The Debug logging level. 15 | /// 16 | Debug, 17 | 18 | /// 19 | /// The Info logging level. 20 | /// 21 | Info, 22 | 23 | /// 24 | /// The Warn logging level. 25 | /// 26 | Warn, 27 | 28 | /// 29 | /// The Error logging level. 30 | /// 31 | Error, 32 | 33 | /// 34 | /// The Fatal logging level. 35 | /// 36 | Fatal, 37 | 38 | /// 39 | /// The Audit logging level. 40 | /// 41 | Audit 42 | } -------------------------------------------------------------------------------- /RockLib.Logging/LogProcessing/FireAndForgetLogProcessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading; 4 | 5 | namespace RockLib.Logging.LogProcessing; 6 | 7 | /// 8 | /// A log processor that processes logs asynchronously, but without any 9 | /// task tracking. 10 | /// 11 | [Obsolete("Please use the FireAndForgetProcessor instead.", false)] 12 | public sealed class FireAndForgetLogProcessor : LogProcessor 13 | { 14 | /// 15 | protected async override void SendToLogProvider(ILogProvider logProvider, LogEntry logEntry, 16 | IErrorHandler errorHandler, int failureCount) 17 | { 18 | try 19 | { 20 | await logProvider.WriteAsync(logEntry, CancellationToken.None).ConfigureAwait(false); 21 | 22 | TraceSource.TraceEvent(TraceEventType.Information, 0, 23 | "[{0:s}] - [" + nameof(FireAndForgetLogProcessor) + "] - Successfully processed log entry {1} from log provider {2}.", 24 | DateTime.UtcNow, logEntry.UniqueId, logProvider); 25 | } 26 | #pragma warning disable CA1031 // Do not catch general exception types 27 | catch (Exception ex) 28 | #pragma warning restore CA1031 // Do not catch general exception types 29 | { 30 | HandleError(ex, logProvider, logEntry, errorHandler, failureCount + 1, 31 | "Error while sending log entry {0} to log provider {1}.", logEntry.UniqueId, logProvider); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /RockLib.Logging/LogProcessing/ILogProcessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Logging.LogProcessing; 4 | 5 | /// 6 | /// Defines an object that processes log entries on behalf of a logger. 7 | /// 8 | public interface ILogProcessor : IDisposable 9 | { 10 | /// 11 | /// Gets a value indicating whether the log processor has been disposed. 12 | /// 13 | bool IsDisposed { get; } 14 | 15 | /// 16 | /// Processes the log entry on behalf of the logger. 17 | /// 18 | /// 19 | /// The logger that the log entry is processed on behalf of. Its log 20 | /// providers and context providers define how the log entry is processed. 21 | /// 22 | /// The log entry to process. 23 | /// 24 | /// Implementations should not call the method 25 | /// on the parameter. 26 | /// 27 | void ProcessLogEntry(ILogger logger, LogEntry logEntry); 28 | } 29 | -------------------------------------------------------------------------------- /RockLib.Logging/LogProcessing/SynchronousLogProcessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading; 4 | 5 | namespace RockLib.Logging.LogProcessing; 6 | 7 | /// 8 | /// A log processor that processes logs on the same thread as the caller. 9 | /// 10 | [Obsolete("Please use the FireAndForgetProcessor instead.", false)] 11 | public sealed class SynchronousLogProcessor : LogProcessor 12 | { 13 | /// 14 | protected override void SendToLogProvider(ILogProvider logProvider, LogEntry logEntry, 15 | IErrorHandler errorHandler, int failureCount) 16 | { 17 | var old = SynchronizationContext.Current; 18 | try 19 | { 20 | SynchronizationContext.SetSynchronizationContext(null); 21 | logProvider.WriteAsync(logEntry, CancellationToken.None).GetAwaiter().GetResult(); 22 | 23 | TraceSource.TraceEvent(TraceEventType.Information, 0, 24 | "[{0:s}] - [" + nameof(SynchronousLogProcessor) + "] - Successfully processed log entry {1} from log provider {2}.", 25 | DateTime.UtcNow, logEntry.UniqueId, logProvider); 26 | } 27 | finally 28 | { 29 | SynchronizationContext.SetSynchronizationContext(old); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /RockLib.Logging/LogProviders/DebugLogProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace RockLib.Logging; 7 | 8 | /// 9 | /// An implementation of that writes log entries to debug. 10 | /// 11 | public class DebugLogProvider : ILogProvider 12 | { 13 | /// 14 | /// The default template. 15 | /// 16 | public const string DefaultTemplate = ConsoleLogProvider.DefaultTemplate; 17 | 18 | /// 19 | /// The default timeout. 20 | /// 21 | public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(1); 22 | 23 | /// 24 | /// Initializes a new instance of the class. 25 | /// 26 | /// The template used to format log entries. 27 | /// The level of the log provider. 28 | /// The timeout of the log provider. 29 | public DebugLogProvider( 30 | string template = DefaultTemplate, LogLevel level = default, TimeSpan? timeout = null) 31 | : this(new TemplateLogFormatter(template ?? DefaultTemplate), level, timeout) 32 | { 33 | } 34 | 35 | /// 36 | /// Initializes a new instance of the class. 37 | /// 38 | /// An object that formats log entries prior to writing to standard out. 39 | /// The level of the log provider. 40 | /// The timeout of the log provider. 41 | public DebugLogProvider( 42 | ILogFormatter formatter, LogLevel level = default, TimeSpan? timeout = null) 43 | { 44 | if (!Enum.IsDefined(typeof(LogLevel), level)) 45 | { 46 | throw new ArgumentException($"Log level is not defined: {level}.", nameof(level)); 47 | } 48 | 49 | if (timeout.HasValue && timeout.Value < TimeSpan.Zero) 50 | { 51 | throw new ArgumentException("Timeout cannot be negative.", nameof(timeout)); 52 | } 53 | 54 | Formatter = formatter ?? throw new ArgumentNullException(nameof(formatter)); 55 | Level = level; 56 | Timeout = timeout ?? DefaultTimeout; 57 | } 58 | 59 | /// 60 | /// Gets an object that formats log entries. 61 | /// 62 | public ILogFormatter Formatter { get; } 63 | 64 | /// 65 | /// Gets the log level. 66 | /// 67 | public LogLevel Level { get; } 68 | 69 | /// 70 | /// Gets the timeout. 71 | /// 72 | public TimeSpan Timeout { get; } 73 | 74 | /// 75 | /// Formats the log entry using the property and writes it to standard out. 76 | /// 77 | /// The log entry to write. 78 | /// The to observe. 79 | /// A task that completes when the log entry has been written to standard out. 80 | public Task WriteAsync(LogEntry logEntry, CancellationToken cancellationToken = default) 81 | { 82 | var formattedLog = Formatter.Format(logEntry); 83 | WriteToDebug(formattedLog); 84 | return Task.CompletedTask; 85 | } 86 | 87 | /// 88 | /// Writes to the class 89 | /// 90 | /// The formatted log message. 91 | protected virtual void WriteToDebug(string formattedLog) => Debug.WriteLine(formattedLog); 92 | } 93 | -------------------------------------------------------------------------------- /RockLib.Logging/LogProviders/ILogFormatter.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Logging; 2 | 3 | /// 4 | /// Defines an object that formats objects to string 5 | /// representations. 6 | /// 7 | public interface ILogFormatter 8 | { 9 | /// 10 | /// Formats the specified log entry as a string. 11 | /// 12 | /// The log entry to format. 13 | /// The formatted log entry. 14 | string Format(LogEntry logEntry); 15 | } 16 | -------------------------------------------------------------------------------- /RockLib.Logging/LogProviders/ILogLevelResolver.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Logging.LogProviders; 2 | 3 | /// 4 | /// Used to resolve the when logging with an 5 | /// 6 | public interface ILogLevelResolver 7 | { 8 | /// 9 | /// Retrieves the currently active 10 | /// 11 | /// The current or null if it cannot be determined 12 | /// If the cannot be determined, and is returned as null, then the default 13 | /// of the will be used instead 14 | LogLevel? GetLogLevel(); 15 | } 16 | -------------------------------------------------------------------------------- /RockLib.Logging/LogProviders/ILogProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace RockLib.Logging; 6 | 7 | /// 8 | /// Defines a object that writes log entries. 9 | /// 10 | /// 11 | /// Implementations of this interface *must* ensure that their method 12 | /// is thread-safe. Multiple threads can invoke the method simultaneously. 13 | /// 14 | public interface ILogProvider 15 | { 16 | /// 17 | /// Gets the timeout of the . 18 | /// 19 | /// 20 | /// If a task returned by the method does not complete 21 | /// by the value of the property, the task will be cancelled. 22 | /// 23 | TimeSpan Timeout { get; } 24 | 25 | /// 26 | /// Gets the level of the . 27 | /// 28 | /// 29 | /// This value is used by the class to determine if it should 30 | /// call this instance's method for a given log entry. If 31 | /// the value of this property is higher than a log entry's level, then this log 32 | /// provider is skipped. 33 | /// 34 | LogLevel Level { get; } 35 | 36 | /// 37 | /// Writes the specified log entry. 38 | /// 39 | /// The log entry to write. 40 | /// The to observe. 41 | /// A task that completes when the log entry has been written. 42 | Task WriteAsync(LogEntry logEntry, CancellationToken cancellationToken); 43 | } -------------------------------------------------------------------------------- /RockLib.Logging/LogProviders/RolloverPeriod.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Logging; 2 | 3 | /// 4 | /// Defines the various rollover periods. 5 | /// 6 | public enum RolloverPeriod 7 | { 8 | /// 9 | /// The rolling file log provider should never archive logs on a periodic basis. 10 | /// 11 | Never, 12 | 13 | /// 14 | /// The rolling file provider should archive logs daily. 15 | /// 16 | Daily, 17 | 18 | /// 19 | /// The rolling file provider should archive logs hourly. 20 | /// 21 | Hourly 22 | } -------------------------------------------------------------------------------- /RockLib.Logging/RockLib.Logging.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Embedded 4 | A simple logging library. 5 | True 6 | True 7 | icon.png 8 | RockLib.Logging 9 | LICENSE.md 10 | https://github.com/RockLib/RockLib.Logging 11 | 6.0.0 12 | A changelog is available at https://github.com/RockLib/RockLib.Logging/blob/main/RockLib.Logging/CHANGELOG.md. 13 | false 14 | RockLib Logging Logger 15 | True 16 | 6.0.0 17 | 18 | 19 | bin\$(Configuration)\$(TargetFramework)\$(PackageId).xml 20 | 21 | 22 | true 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /RockLib.Logging/RockLib.Logging.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30907.101 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Logging", "RockLib.Logging.csproj", "{0F6F91D3-E6A9-4694-9598-D0CAC6B115B3}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Logging.Tests", "..\Tests\RockLib.Logging.Tests\RockLib.Logging.Tests.csproj", "{280C39C8-EB1D-4DE2-A24F-1462F77FF0C7}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {0F6F91D3-E6A9-4694-9598-D0CAC6B115B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {0F6F91D3-E6A9-4694-9598-D0CAC6B115B3}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {0F6F91D3-E6A9-4694-9598-D0CAC6B115B3}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {0F6F91D3-E6A9-4694-9598-D0CAC6B115B3}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {280C39C8-EB1D-4DE2-A24F-1462F77FF0C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {280C39C8-EB1D-4DE2-A24F-1462F77FF0C7}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {280C39C8-EB1D-4DE2-A24F-1462F77FF0C7}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {280C39C8-EB1D-4DE2-A24F-1462F77FF0C7}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {806D89B7-78D1-4038-944B-AFFE3C7A0F00} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /RockLib.Logging/SafeLogging/NotSafeToLogAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | 5 | namespace RockLib.Logging.SafeLogging; 6 | 7 | /// 8 | /// An attribute that signifies a property as not safe for logging. This should be used when a class is 9 | /// marked with the . 10 | /// 11 | [AttributeUsage(AttributeTargets.Property)] 12 | public sealed class NotSafeToLogAttribute : Attribute 13 | { 14 | /// 15 | /// Decorate the specified property with the . 16 | /// 17 | /// The property to decorate. 18 | public static void Decorate(PropertyInfo property) 19 | { 20 | #if NET6_0_OR_GREATER 21 | ArgumentNullException.ThrowIfNull(property); 22 | #else 23 | if (property is null) 24 | { 25 | throw new ArgumentNullException(nameof(property)); 26 | } 27 | #endif 28 | 29 | SanitizeEngine.NotSafeProperties.Add(property); 30 | } 31 | 32 | /// 33 | /// Decorate the property specified by the expression with the . 34 | /// 35 | /// The type of the property to decorate. 36 | /// An expression that defines the property to decorate. 37 | public static void Decorate(Expression> expression) 38 | { 39 | #if NET6_0_OR_GREATER 40 | ArgumentNullException.ThrowIfNull(expression); 41 | #else 42 | if (expression is null) 43 | { 44 | throw new ArgumentNullException(nameof(expression)); 45 | } 46 | #endif 47 | if (expression.Body is MemberExpression memberExpression 48 | && memberExpression.Expression == expression.Parameters[0] 49 | && memberExpression.Member is PropertyInfo property) 50 | { 51 | Decorate(property); 52 | } 53 | else 54 | { 55 | throw new ArgumentException($"Expression does not define a property of type {typeof(T).Name}.", nameof(expression)); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /RockLib.Logging/SafeLogging/SafeToLogAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | 5 | namespace RockLib.Logging.SafeLogging; 6 | 7 | /// 8 | /// An attribute that signifies a property or all the properties of a class are safe to add as extended properties 9 | /// in a log. When used with a class, use the to mark specific properites 10 | /// that should not be logged. 11 | /// 12 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)] 13 | public sealed class SafeToLogAttribute : Attribute 14 | { 15 | /// 16 | /// Decorate the specified type with the . 17 | /// 18 | /// The type to decorate. 19 | public static void Decorate(Type type) 20 | { 21 | #if NET6_0_OR_GREATER 22 | ArgumentNullException.ThrowIfNull(type); 23 | #else 24 | if (type is null) 25 | { 26 | throw new ArgumentNullException(nameof(type)); 27 | } 28 | #endif 29 | SanitizeEngine.SafeTypes.Add(type); 30 | } 31 | 32 | /// 33 | /// Decorate the type of with the . 34 | /// 35 | /// The type to decorate. 36 | public static void Decorate() => Decorate(typeof(T)); 37 | 38 | /// 39 | /// Decorate the specified property with the . 40 | /// 41 | /// The property to decorate. 42 | public static void Decorate(PropertyInfo property) 43 | { 44 | #if NET6_0_OR_GREATER 45 | ArgumentNullException.ThrowIfNull(property); 46 | #else 47 | if (property is null) 48 | { 49 | throw new ArgumentNullException(nameof(property)); 50 | } 51 | #endif 52 | SanitizeEngine.SafeProperties.Add(property); 53 | } 54 | 55 | /// 56 | /// Decorate the property specified by the expression with the . 57 | /// 58 | /// The type of the property to decorate. 59 | /// An expression that defines the property to decorate. 60 | public static void Decorate(Expression> expression) 61 | { 62 | #if NET6_0_OR_GREATER 63 | ArgumentNullException.ThrowIfNull(expression); 64 | #else 65 | if (expression is null) 66 | { 67 | throw new ArgumentNullException(nameof(expression)); 68 | } 69 | #endif 70 | 71 | if (expression.Body is MemberExpression memberExpression 72 | && memberExpression.Expression == expression.Parameters[0] 73 | && memberExpression.Member is PropertyInfo property) 74 | { 75 | Decorate(property); 76 | } 77 | else 78 | { 79 | throw new ArgumentException($"Expression does not define a property of type {typeof(T).Name}.", nameof(expression)); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Tests/RockLib.Logging.AspNetCore.Tests/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | 6 | [*.cs] 7 | # Styling 8 | indent_style = space 9 | indent_size = 4 10 | csharp_indent_case_contents = true 11 | csharp_indent_switch_labels = true 12 | csharp_new_line_before_catch = true 13 | csharp_new_line_before_else = true 14 | csharp_new_line_before_finally = true 15 | csharp_new_line_before_members_in_anonymous_types = false 16 | csharp_new_line_before_members_in_object_initializers = false 17 | csharp_new_line_before_open_brace = methods, control_blocks, types, properties, lambdas, accessors, object_collection_array_initializers 18 | csharp_new_line_between_query_expression_clauses = true 19 | csharp_prefer_braces = false:suggestion 20 | csharp_prefer_simple_default_expression = true:suggestion 21 | csharp_preferred_modifier_order = public,private,internal,protected,static,readonly,async,override,sealed:suggestion 22 | csharp_preserve_single_line_blocks = true 23 | csharp_preserve_single_line_statements = true 24 | csharp_space_after_cast = false 25 | csharp_space_after_colon_in_inheritance_clause = true 26 | csharp_space_after_keywords_in_control_flow_statements = true 27 | csharp_space_before_colon_in_inheritance_clause = true 28 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 29 | csharp_space_between_method_call_name_and_opening_parenthesis = false 30 | csharp_space_between_method_call_parameter_list_parentheses = false 31 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 32 | csharp_space_between_method_declaration_parameter_list_parentheses = false 33 | csharp_style_pattern_matching_over_as_with_null_check = true:error 34 | csharp_style_expression_bodied_accessors = true:error 35 | csharp_style_expression_bodied_constructors = true:error 36 | csharp_style_expression_bodied_methods = true:error 37 | csharp_style_expression_bodied_properties = true:error 38 | csharp_style_namespace_declarations = file_scoped:error 39 | csharp_style_inlined_variable_declaration = true:error 40 | csharp_style_var_elsewhere = true:error 41 | csharp_style_var_for_built_in_types = true:error 42 | csharp_style_var_when_type_is_apparent = true:error 43 | dotnet_sort_system_directives_first = false 44 | dotnet_style_explicit_tuple_names = true:suggestion 45 | dotnet_style_object_initializer = true:suggestion 46 | csharp_style_pattern_local_over_anonymous_function = false:error 47 | dotnet_style_predefined_type_for_member_access = true:error 48 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:error 49 | dotnet_style_prefer_inferred_tuple_names = true:error 50 | dotnet_style_predefined_type_for_locals_parameters_members = true:error 51 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:error 52 | dotnet_style_qualification_for_field = false:error 53 | dotnet_style_qualification_for_method = false:error 54 | dotnet_style_qualification_for_property = false:error 55 | 56 | # Analyzer Configuration 57 | # These are rules we want to either ignore or have set as suggestion or info 58 | 59 | # CA1014: Mark assemblies with CLSCompliant 60 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1014 61 | dotnet_diagnostic.CA1014.severity = none 62 | 63 | # CA1725: Parameter names should match base declaration 64 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1725 65 | dotnet_diagnostic.CA1725.severity = suggestion 66 | 67 | # CA2227: Collection properties should be read only 68 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2227 69 | dotnet_diagnostic.CA2227.severity = suggestion 70 | -------------------------------------------------------------------------------- /Tests/RockLib.Logging.AspNetCore.Tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /Tests/RockLib.Logging.AspNetCore.Tests/InfoLogAttributeTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Xunit; 3 | 4 | namespace RockLib.Logging.AspNetCore.Tests; 5 | 6 | using static LoggingActionFilterAttribute; 7 | using static Logger; 8 | 9 | public class InfoLogAttributeTests 10 | { 11 | [Fact(DisplayName = "Constructor sets properties from non-null parameters")] 12 | public void ConstructorHappyPath1() 13 | { 14 | var messageFormat = "My message format: {0}."; 15 | var loggerName = "MyLogger"; 16 | 17 | var infoLogAttribute = new InfoLogAttribute(messageFormat, loggerName); 18 | 19 | infoLogAttribute.MessageFormat.Should().Be(messageFormat); 20 | infoLogAttribute.LoggerName.Should().Be(loggerName); 21 | infoLogAttribute.LogLevel.Should().Be(LogLevel.Info); 22 | } 23 | 24 | [Fact(DisplayName = "Constructor sets properties from null parameters")] 25 | public void ConstructorHappyPath2() 26 | { 27 | var infoLogAttribute = new InfoLogAttribute(null, null); 28 | 29 | infoLogAttribute.MessageFormat.Should().Be(DefaultMessageFormat); 30 | infoLogAttribute.LoggerName.Should().Be(DefaultName); 31 | infoLogAttribute.LogLevel.Should().Be(LogLevel.Info); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Tests/RockLib.Logging.AspNetCore.Tests/LogBuilderExtensionTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Moq; 5 | using RockLib.Logging.DependencyInjection; 6 | using System; 7 | using System.Collections.Generic; 8 | using Xunit; 9 | 10 | namespace RockLib.Logging.AspNetCore.Tests; 11 | 12 | public class LogBuilderExtensionTests 13 | { 14 | [Fact(DisplayName = "AddHttpContextProvider adds HttpContextProvider")] 15 | public void AddHttpContextProviderExtension() 16 | { 17 | var contextMock = new Mock(); 18 | var loggerBuilder = new TestLoggerBuilder(); 19 | 20 | var serviceProvider = new ServiceCollection() 21 | .AddSingleton(contextMock.Object) 22 | .BuildServiceProvider(); 23 | 24 | loggerBuilder.AddHttpContextProvider(); 25 | 26 | var registration = loggerBuilder.ContextProviderRegistrations.Should().ContainSingle().Subject; 27 | 28 | var contextProvider = registration.Invoke(serviceProvider); 29 | contextProvider.Should().BeOfType(); 30 | } 31 | 32 | private sealed class TestLoggerBuilder : ILoggerBuilder 33 | { 34 | public string LoggerName => Logger.DefaultName; 35 | 36 | public List> LogProviderRegistrations { get; } = new List>(); 37 | 38 | public List> ContextProviderRegistrations { get; } = new List>(); 39 | 40 | public ILoggerBuilder AddLogProvider(Func logProviderRegistration) 41 | { 42 | LogProviderRegistrations.Add(logProviderRegistration); 43 | return this; 44 | } 45 | 46 | public ILoggerBuilder AddContextProvider(Func contextProviderRegistration) 47 | { 48 | ContextProviderRegistrations.Add(contextProviderRegistration); 49 | return this; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Tests/RockLib.Logging.AspNetCore.Tests/RockLib.Logging.AspNetCore.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | false 4 | NU1605,NU1608 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/RockLib.Logging.AspNetCore.Tests/RouteNotFoundMiddlewareExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using FluentAssertions; 4 | using Microsoft.AspNetCore.Builder; 5 | #if !NET5_0 6 | using Microsoft.AspNetCore.Builder.Internal; 7 | #endif 8 | using Microsoft.AspNetCore.Http; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Moq; 11 | using RockLib.Logging.DependencyInjection; 12 | using RockLib.Logging.Moq; 13 | using Xunit; 14 | 15 | namespace RockLib.Logging.AspNetCore.Tests; 16 | 17 | public class RouteNotFoundMiddlewareExtensionsTests 18 | { 19 | [Fact(DisplayName = "UseRouteNotFound adds the RouteNotFoundMiddleware to the pipline")] 20 | public async Task UseRouteNotFoundHappyPath() 21 | { 22 | var path = "/SomePathThing"; 23 | var mockLogger = new MockLogger(); 24 | 25 | var httpResponseMock = new Mock(); 26 | httpResponseMock.Setup(hrm => hrm.StatusCode).Returns(404); 27 | 28 | var httpRequestMock = new Mock(); 29 | httpRequestMock.Setup(requestMock => requestMock.Path).Returns(path); 30 | 31 | var httpContextMock = new Mock(); 32 | httpContextMock.Setup(hcm => hcm.Response).Returns(httpResponseMock.Object); 33 | httpContextMock.Setup(hcm => hcm.Request).Returns(httpRequestMock.Object); 34 | 35 | var services = new ServiceCollection(); 36 | services.AddSingleton(loggerName => mockLogger.Object); 37 | 38 | var applicationBuilder = new ApplicationBuilder(services.BuildServiceProvider()); 39 | 40 | applicationBuilder.UseRouteNotFoundLogging(); 41 | applicationBuilder.Use((c, f) => Task.CompletedTask); 42 | 43 | var pipeline = applicationBuilder.Build(); 44 | 45 | await pipeline(httpContextMock.Object).ConfigureAwait(true); 46 | 47 | mockLogger.Invocations.Count.Should().Be(1); 48 | 49 | var extendedProperties = new Dictionary 50 | { 51 | [RouteNotFoundMiddleware.RouteExtendedPropertiesKey] = new PathString(path) 52 | }; 53 | 54 | mockLogger.VerifyWarn(RouteNotFoundMiddleware.DefaultLogMessage, extendedProperties); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Tests/RockLib.Logging.Microsoft.Extensions.Tests/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | 6 | [*.cs] 7 | # Styling 8 | indent_style = space 9 | indent_size = 4 10 | csharp_indent_case_contents = true 11 | csharp_indent_switch_labels = true 12 | csharp_new_line_before_catch = true 13 | csharp_new_line_before_else = true 14 | csharp_new_line_before_finally = true 15 | csharp_new_line_before_members_in_anonymous_types = false 16 | csharp_new_line_before_members_in_object_initializers = false 17 | csharp_new_line_before_open_brace = methods, control_blocks, types, properties, lambdas, accessors, object_collection_array_initializers 18 | csharp_new_line_between_query_expression_clauses = true 19 | csharp_prefer_braces = false:suggestion 20 | csharp_prefer_simple_default_expression = true:suggestion 21 | csharp_preferred_modifier_order = public,private,internal,protected,static,readonly,async,override,sealed:suggestion 22 | csharp_preserve_single_line_blocks = true 23 | csharp_preserve_single_line_statements = true 24 | csharp_space_after_cast = false 25 | csharp_space_after_colon_in_inheritance_clause = true 26 | csharp_space_after_keywords_in_control_flow_statements = true 27 | csharp_space_before_colon_in_inheritance_clause = true 28 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 29 | csharp_space_between_method_call_name_and_opening_parenthesis = false 30 | csharp_space_between_method_call_parameter_list_parentheses = false 31 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 32 | csharp_space_between_method_declaration_parameter_list_parentheses = false 33 | csharp_style_pattern_matching_over_as_with_null_check = true:error 34 | csharp_style_expression_bodied_accessors = true:error 35 | csharp_style_expression_bodied_constructors = true:error 36 | csharp_style_expression_bodied_methods = true:error 37 | csharp_style_expression_bodied_properties = true:error 38 | csharp_style_namespace_declarations = file_scoped:error 39 | csharp_style_inlined_variable_declaration = true:error 40 | csharp_style_var_elsewhere = true:error 41 | csharp_style_var_for_built_in_types = true:error 42 | csharp_style_var_when_type_is_apparent = true:error 43 | dotnet_sort_system_directives_first = false 44 | dotnet_style_explicit_tuple_names = true:suggestion 45 | dotnet_style_object_initializer = true:suggestion 46 | csharp_style_pattern_local_over_anonymous_function = false:error 47 | dotnet_style_predefined_type_for_member_access = true:error 48 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:error 49 | dotnet_style_prefer_inferred_tuple_names = true:error 50 | dotnet_style_predefined_type_for_locals_parameters_members = true:error 51 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:error 52 | dotnet_style_qualification_for_field = false:error 53 | dotnet_style_qualification_for_method = false:error 54 | dotnet_style_qualification_for_property = false:error 55 | 56 | # Analyzer Configuration 57 | # These are rules we want to either ignore or have set as suggestion or info 58 | 59 | # CA1014: Mark assemblies with CLSCompliant 60 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1014 61 | dotnet_diagnostic.CA1014.severity = none 62 | 63 | # CA1725: Parameter names should match base declaration 64 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1725 65 | dotnet_diagnostic.CA1725.severity = suggestion 66 | 67 | # CA2227: Collection properties should be read only 68 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2227 69 | dotnet_diagnostic.CA2227.severity = suggestion -------------------------------------------------------------------------------- /Tests/RockLib.Logging.Microsoft.Extensions.Tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /Tests/RockLib.Logging.Microsoft.Extensions.Tests/RockLib.Logging.Microsoft.Extensions.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | all 9 | runtime; build; native; contentfiles; analyzers; buildtransitive 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Tests/RockLib.Logging.Moq.Tests/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | 6 | [*.cs] 7 | # Styling 8 | indent_style = space 9 | indent_size = 4 10 | csharp_indent_case_contents = true 11 | csharp_indent_switch_labels = true 12 | csharp_new_line_before_catch = true 13 | csharp_new_line_before_else = true 14 | csharp_new_line_before_finally = true 15 | csharp_new_line_before_members_in_anonymous_types = false 16 | csharp_new_line_before_members_in_object_initializers = false 17 | csharp_new_line_before_open_brace = methods, control_blocks, types, properties, lambdas, accessors, object_collection_array_initializers 18 | csharp_new_line_between_query_expression_clauses = true 19 | csharp_prefer_braces = false:suggestion 20 | csharp_prefer_simple_default_expression = true:suggestion 21 | csharp_preferred_modifier_order = public,private,internal,protected,static,readonly,async,override,sealed:suggestion 22 | csharp_preserve_single_line_blocks = true 23 | csharp_preserve_single_line_statements = true 24 | csharp_space_after_cast = false 25 | csharp_space_after_colon_in_inheritance_clause = true 26 | csharp_space_after_keywords_in_control_flow_statements = true 27 | csharp_space_before_colon_in_inheritance_clause = true 28 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 29 | csharp_space_between_method_call_name_and_opening_parenthesis = false 30 | csharp_space_between_method_call_parameter_list_parentheses = false 31 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 32 | csharp_space_between_method_declaration_parameter_list_parentheses = false 33 | csharp_style_pattern_matching_over_as_with_null_check = true:error 34 | csharp_style_expression_bodied_accessors = true:error 35 | csharp_style_expression_bodied_constructors = true:error 36 | csharp_style_expression_bodied_methods = true:error 37 | csharp_style_expression_bodied_properties = true:error 38 | csharp_style_namespace_declarations = file_scoped:error 39 | csharp_style_inlined_variable_declaration = true:error 40 | csharp_style_var_elsewhere = true:error 41 | csharp_style_var_for_built_in_types = true:error 42 | csharp_style_var_when_type_is_apparent = true:error 43 | dotnet_sort_system_directives_first = false 44 | dotnet_style_explicit_tuple_names = true:suggestion 45 | dotnet_style_object_initializer = true:suggestion 46 | csharp_style_pattern_local_over_anonymous_function = false:error 47 | dotnet_style_predefined_type_for_member_access = true:error 48 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:error 49 | dotnet_style_prefer_inferred_tuple_names = true:error 50 | dotnet_style_predefined_type_for_locals_parameters_members = true:error 51 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:error 52 | dotnet_style_qualification_for_field = false:error 53 | dotnet_style_qualification_for_method = false:error 54 | dotnet_style_qualification_for_property = false:error 55 | 56 | # Analyzer Configuration 57 | # These are rules we want to either ignore or have set as suggestion or info 58 | 59 | # CA1014: Mark assemblies with CLSCompliant 60 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1014 61 | dotnet_diagnostic.CA1014.severity = none 62 | 63 | # CA1725: Parameter names should match base declaration 64 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1725 65 | dotnet_diagnostic.CA1725.severity = suggestion 66 | 67 | # CA2227: Collection properties should be read only 68 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2227 69 | dotnet_diagnostic.CA2227.severity = suggestion -------------------------------------------------------------------------------- /Tests/RockLib.Logging.Moq.Tests/AssemblyInformation.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] -------------------------------------------------------------------------------- /Tests/RockLib.Logging.Moq.Tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /Tests/RockLib.Logging.Moq.Tests/MockLoggerTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Moq; 3 | using Xunit; 4 | 5 | namespace RockLib.Logging.Moq.Tests; 6 | 7 | public class MockLoggerTests 8 | { 9 | [Fact(DisplayName = "Constructor adds setups for Level and Name properties")] 10 | public void ConstructorHappyPath() 11 | { 12 | var mockLogger = new MockLogger(LogLevel.Info, "TestLogger", MockBehavior.Strict); 13 | 14 | mockLogger.Setups.Should().HaveCount(2); 15 | mockLogger.Behavior.Should().Be(MockBehavior.Strict); 16 | mockLogger.Object.Level.Should().Be(LogLevel.Info); 17 | mockLogger.Object.Name.Should().Be("TestLogger"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Tests/RockLib.Logging.Moq.Tests/RockLib.Logging.Moq.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | false 4 | 5 | 6 | 7 | 8 | 9 | 10 | all 11 | runtime; build; native; contentfiles; analyzers; buildtransitive 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Tests/RockLib.Logging.Tests/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | 6 | [*.cs] 7 | # Styling 8 | indent_style = space 9 | indent_size = 4 10 | csharp_indent_case_contents = true 11 | csharp_indent_switch_labels = true 12 | csharp_new_line_before_catch = true 13 | csharp_new_line_before_else = true 14 | csharp_new_line_before_finally = true 15 | csharp_new_line_before_members_in_anonymous_types = false 16 | csharp_new_line_before_members_in_object_initializers = false 17 | csharp_new_line_before_open_brace = methods, control_blocks, types, properties, lambdas, accessors, object_collection_array_initializers 18 | csharp_new_line_between_query_expression_clauses = true 19 | csharp_prefer_braces = false:suggestion 20 | csharp_prefer_simple_default_expression = true:suggestion 21 | csharp_preferred_modifier_order = public,private,internal,protected,static,readonly,async,override,sealed:suggestion 22 | csharp_preserve_single_line_blocks = true 23 | csharp_preserve_single_line_statements = true 24 | csharp_space_after_cast = false 25 | csharp_space_after_colon_in_inheritance_clause = true 26 | csharp_space_after_keywords_in_control_flow_statements = true 27 | csharp_space_before_colon_in_inheritance_clause = true 28 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 29 | csharp_space_between_method_call_name_and_opening_parenthesis = false 30 | csharp_space_between_method_call_parameter_list_parentheses = false 31 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 32 | csharp_space_between_method_declaration_parameter_list_parentheses = false 33 | csharp_style_pattern_matching_over_as_with_null_check = true:error 34 | csharp_style_expression_bodied_accessors = true:error 35 | csharp_style_expression_bodied_constructors = true:error 36 | csharp_style_expression_bodied_methods = true:error 37 | csharp_style_expression_bodied_properties = true:error 38 | csharp_style_namespace_declarations = file_scoped:error 39 | csharp_style_inlined_variable_declaration = true:error 40 | csharp_style_var_elsewhere = true:error 41 | csharp_style_var_for_built_in_types = true:error 42 | csharp_style_var_when_type_is_apparent = true:error 43 | dotnet_sort_system_directives_first = false 44 | dotnet_style_explicit_tuple_names = true:suggestion 45 | dotnet_style_object_initializer = true:suggestion 46 | csharp_style_pattern_local_over_anonymous_function = false:error 47 | dotnet_style_predefined_type_for_member_access = true:error 48 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:error 49 | dotnet_style_prefer_inferred_tuple_names = true:error 50 | dotnet_style_predefined_type_for_locals_parameters_members = true:error 51 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:error 52 | dotnet_style_qualification_for_field = false:error 53 | dotnet_style_qualification_for_method = false:error 54 | dotnet_style_qualification_for_property = false:error 55 | 56 | # Analyzer Configuration 57 | # These are rules we want to either ignore or have set as suggestion or info 58 | 59 | # CA1014: Mark assemblies with CLSCompliant 60 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1014 61 | dotnet_diagnostic.CA1014.severity = none 62 | 63 | # CA1725: Parameter names should match base declaration 64 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1725 65 | dotnet_diagnostic.CA1725.severity = suggestion 66 | 67 | # CA2227: Collection properties should be read only 68 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2227 69 | dotnet_diagnostic.CA2227.severity = suggestion 70 | -------------------------------------------------------------------------------- /Tests/RockLib.Logging.Tests/DelegateErrorHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RockLib.Dynamic; 3 | 4 | namespace RockLib.Logging.Tests; 5 | 6 | internal static class DelegateErrorHandler 7 | { 8 | private const string _delegateErrorHandlerTypeName = "RockLib.Logging.ErrorHandlerExtensions+DelegateErrorHandler, RockLib.Logging"; 9 | private static readonly Type _delegateErrorHandlerType = Type.GetType(_delegateErrorHandlerTypeName, true)!; 10 | 11 | public static IErrorHandler New(Action handleError) => _delegateErrorHandlerType.New(handleError); 12 | } 13 | -------------------------------------------------------------------------------- /Tests/RockLib.Logging.Tests/DependencyInjection/Options/FileLogProviderOptionsTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using RockLib.Logging.DependencyInjection; 3 | using System; 4 | using Xunit; 5 | 6 | namespace RockLib.Logging.Tests.DependencyInjection; 7 | 8 | public static class FileLogProviderOptionsTests 9 | { 10 | [Fact(DisplayName = "File property throws when set to null")] 11 | public static void FilePropertySetToNull() 12 | { 13 | Action act = () => new FileLogProviderOptions().File = null!; 14 | 15 | act.Should().ThrowExactly().WithMessage("*value*"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/RockLib.Logging.Tests/DependencyInjection/Options/FormattableLogProviderOptionsTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Moq; 4 | using RockLib.Logging.DependencyInjection; 5 | using System; 6 | using Xunit; 7 | 8 | namespace RockLib.Logging.Tests.DependencyInjection; 9 | 10 | public static class FormattableLogProviderOptionsTests 11 | { 12 | private static readonly IServiceProvider _emptyServiceProvider = new ServiceCollection().BuildServiceProvider(); 13 | 14 | [Fact(DisplayName = "SetTemplate method sets FormatterRegistration property to TemplateLogFormatter with specified template")] 15 | public static void SetTemplateMethod() 16 | { 17 | var options = new TestFormattableLogProviderOptions(); 18 | 19 | options.SetTemplate("foo"); 20 | 21 | var formatter = options.FormatterRegistration!.Invoke(_emptyServiceProvider); 22 | 23 | formatter.Should().BeOfType() 24 | .Which.Template.Should().Be("foo"); 25 | } 26 | 27 | [Fact(DisplayName = "SetTemplate method throws when template parameter is null")] 28 | public static void SetTemplateMethodWhenTemplateParameterIsNull() 29 | { 30 | var options = new TestFormattableLogProviderOptions(); 31 | 32 | Action act = () => options.SetTemplate(null!); 33 | 34 | act.Should().ThrowExactly().WithMessage("*template*"); 35 | } 36 | 37 | [Fact(DisplayName = "SetFormatter method 1 sets FormatterRegistration property to formatter")] 38 | public static void SetFormatterMethod() 39 | { 40 | var formatter = new Mock().Object; 41 | 42 | var options = new TestFormattableLogProviderOptions(); 43 | 44 | options.SetFormatter(formatter); 45 | 46 | var actualFormatter = options.FormatterRegistration!.Invoke(_emptyServiceProvider); 47 | 48 | actualFormatter.Should().BeSameAs(formatter); 49 | } 50 | 51 | [Fact(DisplayName = "SetFormatter method 1 throws when formatter parameter is null")] 52 | public static void SetFormatterMethodWhenFormatterIsNull() 53 | { 54 | var options = new TestFormattableLogProviderOptions(); 55 | 56 | Action act = () => options.SetFormatter(null!); 57 | 58 | act.Should().ThrowExactly().WithMessage("*formatter*"); 59 | } 60 | 61 | [Fact(DisplayName = "SetFormatter method 2 sets FormatterRegistration property to specified formatter")] 62 | public static void SetFormatterMethodToSpecificFormatter() 63 | { 64 | var options = new TestFormattableLogProviderOptions(); 65 | 66 | options.SetFormatter(123); 67 | 68 | var formatter = options.FormatterRegistration!.Invoke(_emptyServiceProvider); 69 | 70 | formatter.Should().BeOfType() 71 | .Which.Foo.Should().Be(123); 72 | } 73 | 74 | private sealed class TestFormattableLogProviderOptions : FormattableLogProviderOptions 75 | { 76 | } 77 | 78 | #pragma warning disable CA1812 79 | private sealed class TestLogFormatter : ILogFormatter 80 | { 81 | public TestLogFormatter(int foo) => Foo = foo; 82 | 83 | public int Foo { get; } 84 | 85 | public string Format(LogEntry logEntry) => throw new NotImplementedException(); 86 | } 87 | #pragma warning restore CA1812 88 | } 89 | -------------------------------------------------------------------------------- /Tests/RockLib.Logging.Tests/DependencyInjection/Options/RollingFileLogProviderOptionsTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using RockLib.Logging.DependencyInjection; 3 | using Xunit; 4 | 5 | namespace RockLib.Logging.Tests.DependencyInjection; 6 | 7 | public static class RollingFileLogProviderOptionsTests 8 | { 9 | [Fact(DisplayName = "RollingFileLogProviderOptions has the correct default property values")] 10 | public static void DefaultValues() 11 | { 12 | var options = new RollingFileLogProviderOptions(); 13 | 14 | options.MaxFileSizeKilobytes.Should().Be(RollingFileLogProvider.DefaultMaxFileSizeKilobytes); 15 | options.MaxArchiveCount.Should().Be(RollingFileLogProvider.DefaultMaxArchiveCount); 16 | options.RolloverPeriod.Should().Be(RollingFileLogProvider.DefaultRolloverPeriod); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/RockLib.Logging.Tests/DependencyInjection/TestConfigurationProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace RockLib.Logging.Tests.DependencyInjection; 4 | 5 | public class TestConfigurationProvider : ConfigurationProvider 6 | { 7 | public void Reload() => OnReload(); 8 | } 9 | -------------------------------------------------------------------------------- /Tests/RockLib.Logging.Tests/DependencyInjection/TestConfigurationSource.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace RockLib.Logging.Tests.DependencyInjection; 4 | 5 | public class TestConfigurationSource : IConfigurationSource 6 | { 7 | public TestConfigurationProvider Provider { get; } = new TestConfigurationProvider(); 8 | 9 | public IConfigurationProvider Build(IConfigurationBuilder builder) => Provider; 10 | } 11 | -------------------------------------------------------------------------------- /Tests/RockLib.Logging.Tests/Diagnostics/LoggingTraceListenerTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Moq; 3 | using RockLib.Logging.Diagnostics; 4 | using RockLib.Logging.Moq; 5 | using System; 6 | using Xunit; 7 | 8 | namespace RockLib.Logging.Tests.Diagnostics; 9 | 10 | public static class LoggingTraceListenerTests 11 | { 12 | [Fact(DisplayName = "Constructor sets properties")] 13 | public static void Create() 14 | { 15 | var logger = new Mock().Object; 16 | 17 | using var listener = new LoggingTraceListener(logger, LogLevel.Info); 18 | 19 | listener.Logger.Should().BeSameAs(logger); 20 | listener.LogLevel.Should().Be(LogLevel.Info); 21 | } 22 | 23 | [Fact(DisplayName = "Constructor sets LogLevel to logger's level if not provided")] 24 | public static void CreateWithLevelSetFromLogger() 25 | { 26 | var logger = new MockLogger(LogLevel.Info).Object; 27 | 28 | using var listener = new LoggingTraceListener(logger); 29 | 30 | listener.Logger.Should().BeSameAs(logger); 31 | listener.LogLevel.Should().Be(LogLevel.Info); 32 | } 33 | 34 | [Fact(DisplayName = "Constructor sets LogLevel to logger's level if null")] 35 | public static void CreateWhenLevelIsNull() 36 | { 37 | var logger = new MockLogger(LogLevel.Info).Object; 38 | 39 | using var listener = new LoggingTraceListener(logger, null); 40 | 41 | listener.Logger.Should().BeSameAs(logger); 42 | listener.LogLevel.Should().Be(LogLevel.Info); 43 | } 44 | 45 | [Fact(DisplayName = "Constructor throws if logger is null")] 46 | public static void CreateWhenLoggerIsNull() 47 | { 48 | var act = () => new LoggingTraceListener((null as ILogger)!, LogLevel.Info); 49 | 50 | act.Should().ThrowExactly().WithMessage("*logger*"); 51 | } 52 | 53 | [Fact(DisplayName = "Write logs at the correct level")] 54 | public static void Write() 55 | { 56 | var mockLogger = new MockLogger(); 57 | 58 | using var listener = new LoggingTraceListener(mockLogger.Object, LogLevel.Info); 59 | 60 | listener.Write("Hello, world!"); 61 | 62 | mockLogger.VerifyInfo("Hello, world!", Times.Once()); 63 | } 64 | 65 | [Fact(DisplayName = "Write does not log if the level is not high enough for the logger")] 66 | public static void WriteWhenLogLevelIsNotHighEnough() 67 | { 68 | var mockLogger = new MockLogger(LogLevel.Warn); 69 | 70 | using var listener = new LoggingTraceListener(mockLogger.Object, LogLevel.Info); 71 | 72 | listener.Write("Hello, world!"); 73 | 74 | mockLogger.VerifyInfo(Times.Never()); 75 | } 76 | 77 | [Fact(DisplayName = "WriteLine logs at the correct level")] 78 | public static void WriteLineWithLogLevelEqual() 79 | { 80 | var mockLogger = new MockLogger(); 81 | 82 | using var listener = new LoggingTraceListener(mockLogger.Object, LogLevel.Info); 83 | 84 | listener.WriteLine("Hello, world!"); 85 | 86 | mockLogger.VerifyInfo("Hello, world!", Times.Once()); 87 | } 88 | 89 | [Fact(DisplayName = "WriteLine does not log if the level is not high enough for the logger")] 90 | public static void WriteLineWhenLogLevelIsNotHighEnough() 91 | { 92 | var mockLogger = new MockLogger(LogLevel.Warn); 93 | 94 | using var listener = new LoggingTraceListener(mockLogger.Object, LogLevel.Info); 95 | 96 | listener.WriteLine("Hello, world!"); 97 | 98 | mockLogger.VerifyInfo(Times.Never()); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Tests/RockLib.Logging.Tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /Tests/RockLib.Logging.Tests/LogProcessingTests/FakeLogProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace RockLib.Logging.Tests.LogProcessingTests; 6 | 7 | public class FakeLogProvider : ILogProvider 8 | { 9 | public TimeSpan Timeout => TimeSpan.FromSeconds(5); 10 | 11 | public LogLevel Level => LogLevel.NotSet; 12 | 13 | #pragma warning disable 1998 14 | public async Task WriteAsync(LogEntry logEntry, CancellationToken cancellationToken) => throw new NotSupportedException("oh, no."); 15 | #pragma warning restore 1998 16 | } 17 | -------------------------------------------------------------------------------- /Tests/RockLib.Logging.Tests/LogProcessingTests/FireAndForgetLogProcessorTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Moq; 3 | using RockLib.Dynamic; 4 | using RockLib.Logging.LogProcessing; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | 9 | namespace RockLib.Logging.Tests.LogProcessingTests; 10 | 11 | public static class FireAndForgetLogProcessorTests 12 | { 13 | [Fact] 14 | public static void ProcessLogEntryCallsWriteAsyncOnTheLogProvider() 15 | { 16 | #pragma warning disable CS0618 // Type or member is obsolete 17 | using var logProcessor = new FireAndForgetLogProcessor(); 18 | #pragma warning restore CS0618 // Type or member is obsolete 19 | 20 | var mockLogProvider = new Mock(); 21 | var logEntry = new LogEntry(); 22 | 23 | mockLogProvider.Setup(m => m.WriteAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(0)); 24 | 25 | logProcessor.Unlock().SendToLogProvider(mockLogProvider.Object, logEntry, null, 1); 26 | 27 | mockLogProvider.Verify(m => m.WriteAsync(logEntry, It.IsAny()), Times.Once); 28 | } 29 | 30 | [Fact] 31 | public static void IfWriteAsyncThrowsWhileAwaitingHandleErrorIsCalled() 32 | { 33 | #pragma warning disable CS0618 // Type or member is obsolete 34 | using var logProcessor = new FireAndForgetLogProcessor(); 35 | #pragma warning restore CS0618 // Type or member is obsolete 36 | 37 | var logProvider = new FakeLogProvider(); 38 | var logEntry = new LogEntry(); 39 | 40 | Error? capturedError = null; 41 | using var waitHandle = new AutoResetEvent(false); 42 | 43 | var errorHandler = DelegateErrorHandler.New(error => 44 | { 45 | capturedError = error; 46 | waitHandle.Set(); 47 | }); 48 | 49 | logProcessor.Unlock().SendToLogProvider(logProvider, logEntry, errorHandler, 1); 50 | 51 | waitHandle.WaitOne(10000).Should().BeTrue(); 52 | 53 | capturedError.Should().NotBeNull(); 54 | capturedError!.Exception!.Message.Should().Be("oh, no."); 55 | capturedError.LogProvider.Should().BeSameAs(logProvider); 56 | capturedError.LogEntry.Should().BeSameAs(logEntry); 57 | capturedError.FailureCount.Should().Be(2); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Tests/RockLib.Logging.Tests/LogProcessingTests/SynchronousLogProcessorTests.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using RockLib.Dynamic; 3 | using RockLib.Logging.LogProcessing; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace RockLib.Logging.Tests.LogProcessingTests; 9 | 10 | public static class SynchronousLogProcessorTests 11 | { 12 | [Fact] 13 | public static void ProcessLogEntryCallsWriteAsyncOnTheLogProvider() 14 | { 15 | #pragma warning disable CS0618 // Type or member is obsolete 16 | using var logProcessor = new SynchronousLogProcessor(); 17 | #pragma warning restore CS0618 // Type or member is obsolete 18 | 19 | var mockLogProvider = new Mock(); 20 | var logEntry = new LogEntry(); 21 | 22 | mockLogProvider.Setup(m => m.WriteAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(0)); 23 | 24 | logProcessor.Unlock().SendToLogProvider(mockLogProvider.Object, logEntry, null, 1); 25 | 26 | mockLogProvider.Verify(m => m.WriteAsync(logEntry, It.IsAny()), Times.Once); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Tests/RockLib.Logging.Tests/NullErrorHandler.cs: -------------------------------------------------------------------------------- 1 | using RockLib.Dynamic; 2 | using System; 3 | 4 | namespace RockLib.Logging.Tests; 5 | 6 | internal static class NullErrorHandler 7 | { 8 | private const string _nullErrorHandlerTypeName = "RockLib.Logging.LogProcessing.LogProcessor+NullErrorHandler, RockLib.Logging"; 9 | 10 | public static readonly IErrorHandler Instance = 11 | UniversalMemberAccessor.GetStatic(Type.GetType(_nullErrorHandlerTypeName, true)!).Instance; 12 | } 13 | -------------------------------------------------------------------------------- /Tests/RockLib.Logging.Tests/RockLib.Logging.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | false 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | PreserveNewest 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Tests/RockLib.Logging.Tests/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rocklib.logging": { 3 | "Name": "TestLogger", 4 | "Level": "Info", 5 | "LogProviders": { 6 | "type": "RockLib.Logging.ConsoleLogProvider, RockLib.Logging", 7 | "value": { 8 | "template": "foo bar", 9 | "level": "Warn" 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /docs/ContextProviders.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 13 3 | --- 4 | 5 | # Context Providers 6 | 7 | By implementing the `RockLib.Logging.IContextProvider` interface, defining the `AddContext` method, and providing it to a `Logger`, you are able to automatically modify a `LogEntry` whenever it is written to its destination. 8 | 9 | The following implementation of `IContextProvider` will add the version of the operating system (OS) under which the application is running as an extended property to any `LogEntry` emitted by the logger: 10 | 11 | ```csharp 12 | public class OsContextProvider : IContextProvider 13 | { 14 | public void AddContext(LogEntry logEntry) 15 | { 16 | logEntry.SetExtendedProperties(new {OSVersion = System.Environment.OSVersion.VersionString}); 17 | } 18 | } 19 | ``` 20 | 21 | Context providers are passed to the `Logger` upon instantiation via an array of type `IContextProvider`. For example, the following code will provide a new instance of the above context provider to a new `Logger`: 22 | 23 | ```csharp 24 | var logger = new Logger(contextProviders: new [] { new OsContextProvider() }); 25 | ``` 26 | 27 | Alternatively, context providers can be assigned to a `Logger` by means of configuration. In the following example, we will assign our context provider to the default `Logger`: 28 | 29 | ```csharp 30 | { 31 | "RockLib.Logging": { 32 | "LogProviders": { 33 | "Type": "RockLib.Logging.ConsoleLogProvider, RockLib.Logging" 34 | }, 35 | "ContextProviders": { 36 | "Type": "MyProject.OsContextProvider, MyProject" 37 | } 38 | } 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/Formatting.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 16 3 | --- 4 | 5 | # Formatting Logs 6 | 7 | By implementing the `RockLib.Logging.ILogFormatter` interface, defining the `Format` method, and providing it to a log provider (such as a `FileLogProvider`, a `ConsoleLogProvider` or a `RollingFileLogProvider`), you are able to transform a `LogEntry` object into a `string` emitted by the log provider. 8 | 9 | As an example, the following implementation of `ILogFormatter` will output `LogEntry.Message` followed by the Log `LogEntry.CreateTime`, with each separated by a hyphen. 10 | 11 | ```csharp 12 | public class SimpleMessageFormatter : ILogFormatter 13 | { 14 | public string Format(LogEntry logEntry) 15 | { 16 | // output Message - CreateTime (in ISO8601 format) 17 | return $"{logEntry.Message} - {logEntry.CreateTime.ToString("o")}"; 18 | } 19 | } 20 | ``` 21 | 22 | This would result in the following message being output by the consuming log provider when it is prompted to log: 23 | ``` 24 | SAMPLE LOG MESSAGE - 2025-06-10T16:20:57.7876207Z 25 | ``` 26 | 27 | Log formatters are passed to the Log provider via the constructor. For example, the following code will provide a new instance of the above message formatter to a new `FileLogProvider`: 28 | 29 | ```csharp 30 | var fileLogProvider = new FileLogProvider("log.txt", new SimpleMessageFormatter()); 31 | ``` 32 | -------------------------------------------------------------------------------- /docs/GettingStarted.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Getting Started 6 | 7 | In this tutorial, we will be building a console application with a hosted service that writes logs when started and stopped (the service doesn't actually do anything). 8 | 9 | --- 10 | 11 | Create a .NET Core console application named "LoggingTutorial". 12 | 13 | --- 14 | 15 | Add nuget references for "RockLib.Logging", "Microsoft.Extensions.DependencyInjection" and "Microsoft.Extensions.Hosting" to the project. 16 | 17 | --- 18 | 19 | ## Add a new class named 'ExampleService' to the project: 20 | 21 | ```csharp 22 | using Microsoft.Extensions.Hosting; 23 | using RockLib.Logging; 24 | using System.Threading; 25 | using System.Threading.Tasks; 26 | 27 | namespace LoggingTutorial 28 | { 29 | public class ExampleService : IHostedService 30 | { 31 | private readonly ILogger _logger; 32 | 33 | public ExampleService(ILogger logger) 34 | { 35 | _logger = logger; 36 | } 37 | 38 | public Task StartAsync(CancellationToken cancellationToken) 39 | { 40 | _logger.Info("Starting service..."); 41 | return Task.CompletedTask; 42 | } 43 | 44 | public Task StopAsync(CancellationToken cancellationToken) 45 | { 46 | _logger.Info("Stopping service..."); 47 | return Task.CompletedTask; 48 | } 49 | } 50 | } 51 | ``` 52 | 53 | --- 54 | 55 | ## Edit the _Program.cs_ file as follows: 56 | 57 | ```csharp 58 | using Microsoft.Extensions.DependencyInjection; 59 | using Microsoft.Extensions.Hosting; 60 | using RockLib.Logging.DependencyInjection; 61 | using System.Threading.Tasks; 62 | 63 | namespace LoggingTutorial 64 | { 65 | class Program 66 | { 67 | static Task Main(string[] args) 68 | { 69 | return CreateHostBuilder(args) 70 | .RunConsoleAsync(options => options.SuppressStatusMessages = true); 71 | } 72 | 73 | static IHostBuilder CreateHostBuilder(string[] args) 74 | { 75 | return Host.CreateDefaultBuilder(args).ConfigureServices(services => 76 | { 77 | services.AddLogger() 78 | .AddConsoleLogProvider(options => options.SetTemplate("[{createTime(O)}] {level} Log: {message}")); 79 | 80 | services.AddHostedService(); 81 | }); 82 | } 83 | } 84 | } 85 | ``` 86 | 87 | --- 88 | 89 | ## Start the app 90 | 91 | It should output something similar to the following to console: 92 | 93 | ``` 94 | [2020-07-17T15:33:58.9262051Z] Info Log: Starting service... 95 | ``` 96 | 97 | --- 98 | 99 | ## Stop the app 100 | 101 | After hitting ` + c`, it should output this, then exit: 102 | 103 | ``` 104 | [2020-07-17T15:35:38.5859970Z] Info Log: Stopping service... 105 | ``` 106 | -------------------------------------------------------------------------------- /docs/LogLevelResolver.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 15 3 | --- 4 | 5 | # Log Level Resolvers 6 | 7 | The `ILogLevelResolver` interface defines a custom method in which any given `ILogger` can use to retrieve it's `LogLevel` on-demand. 8 | 9 | > **Note:** `ILogLevelResolver` does not affect the logging level for other default loggers (such as `Microsoft.Extensions.Logging.ILogger`). It will only affect logs routed through `RockLib.Logging.ILogger`. 10 | 11 | ## Creating Your Own Resolver 12 | 13 | New log level resolvers can be created by implementing the `ILogLevelResolver` interface, and registering the implementation to the DI container. If no DI container is used, you may pass `ILogLevelResolver` directly into the constructor of a `Logger`. 14 | 15 | Only one `ILogLevelResolver` may be used per application when DI is used. 16 | 17 | ### Implementation 18 | 19 | The interface `ILogLevelResolver` contains one method - `GetLogLevel()`. This method can return a `LogLevel`, or `null`. When a `LogLevel` is returned, that `LogLevel` will be used for all messages. When `null` is returned, the `ILogger` will fall back to it's primary method of obtaining the logging level (e.g. via an `appsettings.json` configuration). 20 | 21 | ## Use Cases 22 | 23 | This interface can be useful if you would like your application to be able to switch logging levels at runtime. When adding a custom `ILogLevelResolver`, the implementation itself can be setup to pull log levels from wherever you would like. 24 | -------------------------------------------------------------------------------- /docs/LogProcessors.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 14 3 | --- 4 | 5 | # Log Processors 6 | 7 | `LogProcessors` provide different ways of sending logs to `LogProviders`. 8 | 9 | `RockLib.Logging` comes with three `LogProcessors` available for use. 10 | 11 | ## BackgroundLogProcessor 12 | 13 | The `BackgroundLogProcessor` is the default `LogProcessor` used in `RockLib.Logging`. 14 | 15 | This `LogProcessor` processes and tracks logs on dedicated non-threadpool background threads. When this is disposed of, it will block until all in-flight logs have finished processing. 16 | 17 | This behavior ensures that, on a graceful shutdown of an application, logs will still be sent to their destination. 18 | 19 | ## FireAndForgetLogProcessor 20 | 21 | The `FireAndForgetLogProcessor` processes logs asynchronously, but without any task tracking. 22 | 23 | As its name implies, these logs are not tracked and, in the event that the application shuts down before the logs have been processed, they may be lost. This is best used when logs from this application are not of great importance. 24 | 25 | ## SynchronousLogProcessor 26 | 27 | The `SynchronousLogProcessor` processes logs on the same thread as the caller. 28 | 29 | This behavior ensures that logs will be processed before the application can continue work. This is best used when logging is a necessary component of the application's operation. 30 | 31 | ## Creating Your Own LogProcessor 32 | 33 | The above-mentioned implementations of `LogProcessor` should cover the majority of use cases for logging. However, if these do not meet the requirements for your application, you can implement a new log processor by having the new class implement the `ILogProcessor` interface or inherit the `LogProcessor` class. 34 | 35 | ## ILogProcessor 36 | 37 | The `ILogProcessor` interface is available for implementation if custom logic is needed for processing logs. The `IsDisposed` property and `ProcessLogEntry` method both need to be defined. 38 | 39 | **Note:** when defining the `ProcessLogEntry` method, `ILogger.Log` should not be called. 40 | 41 | ## LogProcessor 42 | 43 | The abstract `LogProcessor` class implements `ILogProcessor`. The `SendToLogProvider` abstract method must be defined. 44 | 45 | Additionally, the folllowing virtual members provide base functionality, but are available for override if necessary: 46 | 47 | * `Dispose` 48 | * `ProcessLogEntry` 49 | * `HandleError` 50 | -------------------------------------------------------------------------------- /docs/LogProviderErrors.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 12 3 | --- 4 | 5 | # Handle Log Provider Errors 6 | 7 | By default, errors produced by log providers are only traced, see [Tracing](Tracing.md). In order to directly handle the errors, the `ILogger.ErrorHandler` can be set, either directly or through an extension method. 8 | 9 | ## Error Type 10 | 11 | **Message** 12 | - Type: string 13 | - Description: Gets the message that describes the error. 14 | 15 | **Exception** 16 | - Type: Exception 17 | - Description: Gets the exception responsible for the error. 18 | 19 | **LogProvider** 20 | - Type: ILogProvider 21 | - Description: Gets the log provider that failed to write the log entry. 22 | 23 | **LogEntry** 24 | - Type: LogEntry 25 | - Description: Gets the log entry that failed to write. 26 | 27 | **FailureCount** 28 | - Type: int 29 | - Description: Gets the number of times the log provider has failed to write the log entry. 30 | 31 | **IsTimeout** 32 | - Type: bool 33 | - Description: Gets a value indicating whether the error was a result of timing out. 34 | 35 | **Timestamp** 36 | - Type: DateTime 37 | - Description: Gets the time that the error event occurred. 38 | 39 | **ShouldRetry** 40 | - Type: bool 41 | - Description: Gets or sets a value indicating whether the log provider should attempt to send the log entry again. 42 | 43 | ## SetErrorHandler Extension 44 | 45 | The `SetErrorHandler` extension uses a `DelegateErrorHandler` behind the scenes. This will invoke whatever action is passed into it with the given `Error`. 46 | 47 | ```csharp 48 | logger.SetErrorHandler(e => 49 | { 50 | // Do something with the error here. 51 | }) 52 | ``` 53 | 54 | ## Directly Setting ErrorHandler Property 55 | 56 | In order to directly set the `ErrorHandler` property, there needs to be a new implementation of the `IErrorHandler` interface. There are currently no public implementations of the interface. 57 | 58 | ```csharp 59 | logger.ErrorHandler = new ExampleErrorHandler(); 60 | 61 | public class ExampleErrorHandler : IErrorHandler 62 | { 63 | public void HandleError(Error error) 64 | { 65 | // Do something with the error here. 66 | } 67 | } 68 | ``` 69 | 70 | ## Adding Retry On Error 71 | 72 | One possibility for handling the errors is to retry sending to the provider. If the `ShouldRetry` property is set to `true` when `HandleError` is called, the provider will be retried. Each attempt will increment the `FailureCount` property. 73 | 74 | ### Simple example of adding retry: 75 | 76 | ```csharp 77 | logger.SetErrorHandler(e => 78 | { 79 | if (e.FailureCount < 4) 80 | e.ShouldRetry = true; 81 | }) 82 | ``` 83 | -------------------------------------------------------------------------------- /docs/LogProviders.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 8 3 | sidebar_label: 'Use log providers' 4 | --- 5 | 6 | # Log Providers 7 | 8 | The `ILogProvider` interface defines exactly how logs are written; a logger can have zero to many log providers. `RockLib.Logging` comes with three implementations of the `ILogProvider`. 9 | 10 | ## [ConsoleLogProvider](ConsoleLogProvider.md) 11 | 12 | Writes log entries to standard out or standard error. 13 | 14 | ## [FileLogProvider](FileLogProvider.md) 15 | 16 | Writes log entries to a file. 17 | 18 | ## [RollingFileLogProvider](RollingFileLogProvider.md) 19 | 20 | Writes log entries to a file. Log files will then be archived based on time and filesize configuration. 21 | 22 | ## Creating Your Own LogProvider 23 | 24 | New log providers can be created by implementing the `ILogProvider` interface. 25 | 26 | ## ILogProvider 27 | 28 | The `ILogProvider` interface has `Timeout` and `Level` properties, as well as a `WriteAsync` method. 29 | 30 | **Timeout Property** 31 | - Type: TimeSpan 32 | - Description: If a task returned by the `WriteAsync` method does not complete by the value of the `Timeout` property, the task will be cancelled. 33 | 34 | **Level Property** 35 | - Type: LogLevel enum (Debug, Info, Warn, Error, Fatal, Audit) 36 | - Description: This value is used by the `Logger` class to determine if it should call this instance's `WriteAsync` method for a given log entry. If the value of this property is higher than a log entry's level, then this log provider is skipped. 37 | 38 | **WriteAsync Method** 39 | - Description: Writes the specified log entry. 40 | - Parameters: 41 | - logEntry 42 | - Type: LogEntry 43 | - Description: The log entry to write. 44 | - cancellationToken 45 | - Type: CancellationToken. 46 | - Description: The `CancellationToken` to observe. 47 | - Returns: A task that completes when the log entry has been written. 48 | -------------------------------------------------------------------------------- /docs/Logger.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | sidebar_label: 'Instantiate and configure a logger' 4 | --- 5 | 6 | # How to instantiate and configure a logger 7 | 8 | The `Logger` class can be directly instantiated and has two public constructors. The only difference between the constructors is how its log processor is initialized (whether by enum or directly). It also has one read/write property. 9 | 10 | ## Constructor 1: 11 | 12 | Name | Type | Description | Required | Default Value 13 | ---- | ---- | ----------- | -------- | ------------- 14 | name | `string` | The name of the logger. | No | `"default"` 15 | level | `enum LogLevel`: `NotSet`, `Debug`, `Info`, `Warn`, `Error`, `Fatal`, `Audit` | The logging level of the logger. Logs with a level lower than this are not processed. | No | `NotSet` 16 | logProviders | `IReadOnlyCollection` | A collection of `ILogProvider` objects used by this logger. | No | Empty list 17 | isDisabled | `bool` | Whether the logger should be disabled. | No | `false` 18 | processingMode | `enum ProcessingMode`: `Background`, `Synchronous`, `FireAndForget` | A value that indicates how the logger will process logs. | No | `Background` 19 | contextProviders | `IReadOnlyCollection` | A collection of `IContextProvider` objects that customize outgoing log entries. | No | Empty list 20 | 21 | ## Constructor 2: 22 | 23 | Name | Type | Description | Required | Default Value 24 | ---- | ---- | ----------- | -------- | ------------- 25 | logProcessor | `ILogProcessor` | The object responsible for processing logs. | Yes | N/A 26 | name | `string` | The name of the logger. | No | `"default"` 27 | level | `enum LogLevel`: `NotSet`, `Debug`, `Info`, `Warn`, `Error`, `Fatal`, `Audit` | The logging level of the logger. Logs with a level lower than this are not processed. | No | `NotSet` 28 | logProviders | `IReadOnlyCollection` | A collection of `ILogProvider` objects used by this logger. | No | Empty list 29 | isDisabled | `bool` | Whether the logger should be disabled. | No | `false` 30 | contextProviders | `IReadOnlyCollection` | A collection of `IContextProvider` objects that customize outgoing log entries. | No | Empty list 31 | 32 | ## Read / write properties 33 | 34 | Name | Type | Description | Default Value 35 | ---- | ---- | ----------- | ------------- 36 | ErrorHandler | `IErrorHandler` | An object that handles errors that occur during log processing. | `null` 37 | -------------------------------------------------------------------------------- /docs/LoggingTraceListener.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 18 3 | --- 4 | 5 | # Use LoggingTraceListener to log trace messages 6 | 7 | The `RockLib.Logging.Diagnostics.LoggingTraceListener` is an inheritor of `System.Diagnostics.TraceListener` that sends trace messages to a `RockLib.Logging.ILogger`. It is for troubleshooting *other* libraries that output trace messages - such trace messages will be logged with the `Ilogger` that the application chooses. 8 | 9 | > **WARNING:** DO NOT use `LoggingTraceListener` for troubleshooting issues with RockLib.Logging itself! 10 | 11 | ## RockLib.Diagnostics Configuration 12 | 13 | The easiest way to add a `LoggingTraceListener` is with configuration. The following example assumes that the library we're troubleshooting uses a trace source named "my_library_trace_source_name". 14 | 15 | ```json 16 | { 17 | "Rocklib.Logging": { 18 | "Level": "Debug", 19 | "Providers": { "Type": "RockLib.Logging.ConsoleLogProvider, RockLib.Logging" } 20 | }, 21 | 22 | "RockLib.Diagnostics": { 23 | "Sources": { 24 | "Name": "my_library_trace_source_name", 25 | "Switch": { 26 | "Name": "my_library_trace_source_name", 27 | "Level": "All" 28 | }, 29 | "Listeners": { "Type": "RockLib.Logging.Diagnostics.LoggingTraceListener, RockLib.Logging" } 30 | } 31 | } 32 | } 33 | ``` 34 | 35 | If the application defines more than one logger, the `LoggingTraceListener` needs to specify the logger name. 36 | 37 | ```json 38 | { 39 | "Rocklib.Logging": [ 40 | { 41 | "Name": "MainLogger", 42 | "Level": "Warn", 43 | "Providers": { "Type": "RockLib.Logging.ConsoleLogProvider, RockLib.Logging" } 44 | }, 45 | { 46 | "Name": "DiagnosticLogger", 47 | "Level": "Debug", 48 | "Providers": { "Type": "RockLib.Logging.ConsoleLogProvider, RockLib.Logging" } 49 | } 50 | ] 51 | 52 | "RockLib.Diagnostics": { 53 | "Sources": { 54 | "Name": "my_library_trace_source_name", 55 | "Switch": { 56 | "Name": "my_library_trace_source_name", 57 | "Level": "All" 58 | }, 59 | "Listeners": { 60 | "Type": "RockLib.Logging.Diagnostics.LoggingTraceListener, RockLib.Logging", 61 | "Value": { 62 | "LoggerName": "DiagnosticLogger" 63 | } 64 | } 65 | } 66 | } 67 | } 68 | ``` 69 | -------------------------------------------------------------------------------- /docs/Reloading.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 20 3 | --- 4 | 5 | # Changing a logger's settings "on the fly" 6 | 7 | It is generally a bad idea to run an application in production while logging at the `Debug` (or arguably the `Info`) level. Especially when the logging comes at a price per log. Millions or billions of debug or info logs could be quite expensive. On the other hand, production issues can and do arise, and having those `Debug` or `Info` logs available for diagnostics is incredibly valuable. A solution to this problem is to normally log at `Warn`, but have the ability to change the level to `Info` or `Debug` on-the-fly, should the need arise. 8 | 9 | To accomplish this, RockLib.Logging binds to configuration. Microsoft.Extensions.Configuration supports automatically reloading itself when its settings change - this is the mechanism that RockLib hooks into to keep its loggers up-to-date. The Json congiruation provider supports this reloading capability, so it is easy to verify that your loggers have the most up-to-date settings on a developer machine: edit the appsettings.json file while the app is running and set a breakpoint to examine your logger - it should stay up-to-date. 10 | 11 | ### Up-to-date Loggers 12 | 13 | When registering and defining a logger with DI extension methods, in order to have up-to-date loggers, bind the logger's `LoggerOptions` to a configuration section: 14 | 15 | ```csharp 16 | services.AddLogger().AddConsoleLogProvider(); 17 | services.Configure(Configuration.GetSection("MyLogger")); 18 | ``` 19 | 20 | ```json 21 | { 22 | "MyLogger": { 23 | "Level": "Warn" 24 | } 25 | } 26 | ``` 27 | 28 | --- 29 | 30 | It is also possible to register a logger with DI, but define it in configuration using a standard `LoggerFactory` configuration section. In this case, it is not necessary to bind options. 31 | 32 | ```csharp 33 | // An empty logger builder will create the logger defined in configuration using COF. 34 | services.AddLogger(); 35 | ``` 36 | 37 | ```json 38 | { 39 | "Rocklib.Logging": { 40 | "Level": "Warn", 41 | "Providers": { "Type": "RockLib.Logging.ConsoleLogProvider, RockLib.Logging" } 42 | } 43 | } 44 | ``` 45 | 46 | ### Reloading Loggers 47 | 48 | For applications with short-lived, transient or scopped loggers (like ASP.NET Core), there's nothing else to do. Since loggers are created at the time they are used, and since they are created with the most recent configuration, they will always be up-to-date. However, for singleton or other long-lived loggers, we need reloading loggers - they should be notified of configuration changes so they can update themselves accordingly. 49 | 50 | --- 51 | 52 | When registering and defining a logger with DI, in order to have reloading loggers, make sure the `ReloadOnChange` setting of the options is true: 53 | 54 | ```csharp 55 | services.AddLogger().AddConsoleLogProvider(); 56 | services.Configure(Configuration.GetSection("MyLogger")); 57 | ``` 58 | 59 | ```json 60 | { 61 | "MyLogger": { 62 | "ReloadOnChange": true, 63 | "Level": "Warn" 64 | } 65 | } 66 | ``` 67 | 68 | --- 69 | 70 | When registering with DI and defining in configuration with a `LoggerFactory` section, specifiy the `reloadOnChange` parameter like this: 71 | 72 | ```json 73 | { 74 | "Rocklib.Logging": { 75 | "ReloadOnChange": true, 76 | "Value": { 77 | "Level": "Warn", 78 | "Providers": { "Type": "RockLib.Logging.ConsoleLogProvider, RockLib.Logging" } 79 | } 80 | } 81 | } 82 | ``` 83 | 84 | --- 85 | 86 | When using LoggerFactory or the extension methods defined by the LoggerFactoryExtensions class, there is a parameter, `reloadOnConfigChange`, which is true by default. Set the parameter to false to *disable* reloading. 87 | 88 | ```csharp 89 | ILogger logger = LoggerFactory.CreateLogger(reloadOnConfigChange: false); 90 | ``` 91 | -------------------------------------------------------------------------------- /docs/Tracing.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 17 3 | --- 4 | 5 | # Enable Tracing For Troubleshooting 6 | 7 | It is possible for an implementation of `ILogProvider` to throw an exception in its `WriteAsync` method (in fact implementations should throw any exception instead of catching and handling it). If an exception is thrown, it will caught by the log processor, passed to the logger's `ErrorHandler`, and if a `TraceSource` exists with the name "rocklib.logging", a trace event is sent to it. 8 | 9 | In addition, if the `TraceSource` is defined and is tracing is at `Information`, a trace event is sent for each successful log processing. 10 | 11 | ## To set a _TraceSource_, add this to your configuration: 12 | 13 | ```json 14 | "RockLib.Diagnostics": { 15 | "Sources": { 16 | "Name": "rocklib.logging", 17 | "Switch": { 18 | "Name": "rocklib.logging", 19 | "Level": "All" 20 | }, 21 | "Listeners": { 22 | "Name": "rocklib.logging", 23 | "LogFileName": "C:\\my\\path\\rocklib_logging.log" 24 | } 25 | } 26 | } 27 | ``` 28 | 29 | ## To set the _TraceSource_ programmatically: 30 | 31 | ```csharp 32 | Tracing.Settings = new DiagnosticsSettings( 33 | sources: new System.Diagnostics.TraceSource[] 34 | { 35 | new System.Diagnostics.TraceSource(name: "rocklib.logging") 36 | { 37 | Switch = new System.Diagnostics.SourceSwitch(name: "rocklib.logging") 38 | { 39 | Level = System.Diagnostics.SourceLevels.All 40 | }, 41 | Listeners = 42 | { 43 | new System.Diagnostics.DefaultTraceListener 44 | { 45 | Name = "rocklib.logging", 46 | LogFileName = "C:\\my\\path\\rocklib_logging.log" 47 | } 48 | } 49 | } 50 | }); 51 | ``` 52 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RockLib/RockLib.Logging/f76d19604961b5395d7f3dd741b14ffe24f908f9/icon.png --------------------------------------------------------------------------------