Logging with H.Necessaire

Logging mechanism of H.Necessaire; logging is basic building block in any app, and therefore H.Necessaire tries to make it as painless and simple as possible.

Logging with H.Necessaire
Photo by Beatriz Pérez Moya / Unsplash

Logging is a basic building block in any app, and therefore H.Necessaire tries to make it as painless and simple as possible.

Usage

dotnet add package H.Necessaire

H.Necessaire

ImALogger logger = depsProvider.GetLogger<Program>();

await logger.LogInfo("Hello H.Necessaire logging");

Logging usage in H.Necessaire

ImADependencyProvider depsProvider
    = H.Necessaire.IoC
    .NewDependencyRegistry()
    .Register<HNecessaireDependencyGroup>(() => new HNecessaireDependencyGroup())
    ;

Creating a new IoC-DI container with H.Necessaire Core dependencies

H.Necessaire.Usage.Samples/Src/H.Necessaire.Samples/H.Necessaire.Samples.Logging/Program.cs at master · hinteadan/H.Necessaire.Usage.Samples
Usage Samples for H.Necessaire Docs WebSite. Contribute to hinteadan/H.Necessaire.Usage.Samples development by creating an account on GitHub.

Behind the scenes

Though the usage is simple, under the hood, there's quite a path to be walked between the Log() call and the actual processing. This path is described in the diagram below and it's essential for identifying logging extension points.

flowchart TD
    depsProvider[depsProvider : **ImADependencyProvider**] --> |GetLogger|LoggerFactory[LoggerFactory : **ImALoggerFactory**]
    LoggerFactory --> |Get new InMemoryLogger from depsProvider|InMemoryLogger

    InMemoryLogger[InMemoryLogger : LoggerBase : **ImALogger**] --> |**Log**|internalLogDictionary[**Store LogEntry in memory**]
  
    InMemoryLogger[InMemoryLogger : LoggerBase : **ImALogger**] --> |Process Log Entries|InMemoryLogProcessorRegistry[LogProcessorRegistry : **ImALogProcessorRegistry**]
    InMemoryLogProcessorRegistry --> |For each IoC registered log processor|LogProcessor[LogProcessor : LogProcessorBase : **ImALogProcessor**]
    LogProcessor --> |For each log entry|IsEligible{Is Eligible}
    IsEligible --> |No|StopNotEligible[[🛑]]
    IsEligible --> |Yes|Process[✅**Process Log Entry**]

    Process --> |ConsoleLogProcessor : **ImALogProcessor**|LogToConsole[Log to **console**]
    Process --> |FileSystemLogProcessor : **ImALogProcessor**|LogToFiles[Log to **files**]
    Process --> |DbLogProcessor : **ImALogProcessor**|LogToExceptionless[Log to **database**]
    Process --> |CustomLogProcessor : **ImALogProcessor**|LogToCustom[Log to **_your custom implementation_**]

Out of the box

H.Necessaire suite comes with various out-of-the-box ImALogProcessor implementations.

H.Necessaire Core

  • ConsoleLogProcessor - outputs the logs into console. This is the single default logging when you're using just the core nuget.

H.Necessaire.Runtime

  • PersistentLogProcessor - saves the logs in the persistent storage registered as ImAStorageService<Guid, LogEntry>, which defaults to the File System.
    • When a dedicated runtime nuget is being used (e.g.: SQL, RavenDB, Azure CosmosDB, Google FirestoreDB, etc.), the file system storage is replaced by the underlying runtime storage.
    • Or, if a custom implementation of ImAStorageService<Guid, LogEntry> is registered, then that's what it'll be used.

Dedicated Log Services

There are dedicated logging services available, such as Azure AppInsights, Exceptionless, NewRelic, etc.

H.Necessaire offers out-of-the-box integrations with Exceptionless and NewRelic via explicit NuGets.

Exceptionless

H.Necessaire.Exceptionless

dotnet add package H.Necessaire.Exceptionless
ImADependencyProvider depsProvider
    = H.Necessaire.IoC
    .NewDependencyRegistry()
    .Register<HNecessaireDependencyGroup>(() => new HNecessaireDependencyGroup())
    .Register<H.Necessaire.Exceptionless.ExceptionlessDependencyGroup>(() => new H.Necessaire.Exceptionless.ExceptionlessDependencyGroup())
    ;

NewRelic

H.Necessaire.Logging.NewRelic

dotnet add package H.Necessaire.Logging.NewRelic
ImADependencyProvider depsProvider
    = H.Necessaire.IoC
    .NewDependencyRegistry()
    .Register<HNecessaireDependencyGroup>(() => new HNecessaireDependencyGroup())
    .Register<H.Necessaire.Logging.NewRelic.NewRelicLoggingDependencyGroup>(() => new H.Necessaire.Logging.NewRelic.NewRelicLoggingDependencyGroup())
    ;

Extending H.Necessaire Logging

If you've read this far, you probably figured it out that the logging mechanism is very easy to extend.

You simply need to implement a concrete ImALogProcessor and register it in the DI container at app start.

Example:

internal class MyCustomLogProcessor : ImALogProcessor
{
    public ImALogProcessor ConfigWith(LogConfig logConfig) => this;

    public LoggerPriority GetPriority() => LoggerPriority.Immediate;

    public Task<bool> IsEligibleFor(LogEntry logEntry) => true.AsTask();

    public Task<OperationResult<LogEntry>> Process(LogEntry logEntry)
    {
        Console.WriteLine("~~~CustomLogProcessor BEGIN~~~");
        Console.WriteLine(logEntry.Message);
        Console.WriteLine("~~~CustomLogProcessor END~~~");
        return OperationResult.Win().WithPayload(logEntry).AsTask();
    }
}

[...]

ImADependencyProvider depsProvider
    = H.Necessaire.IoC
    .NewDependencyRegistry()
    .Register<HNecessaireDependencyGroup>(() => new HNecessaireDependencyGroup())
    .Register<MyCustomLogProcessor>(() => new MyCustomLogProcessor())
    ;