nreco / logging

Generic file logger for .NET Core (FileLoggerProvider) with minimal dependencies
MIT License
284 stars 56 forks source link

Need support for formatting filename based on category name #41

Closed CDboyOne closed 2 years ago

CDboyOne commented 2 years ago

When formatting logging filename, I think the category name needs to be added as a parameter, too.

VitaliyMF commented 2 years ago

Writing to multiple files simultaneously is not possible for this FileLoggingProvider implementation - it is designed to have only one opened file to write log entries.

However, it might be possible to support what do you want in another way. It makes sense to have multiple file logging providers - each one with its own file & configuration - that can filter log messages by a category (INFO, WARNING, ERROR etc). Currently it is possible to achieve this by setting FileLoggerOptions.MinLevel, and in this way you can have a log file only with, say, ERRORs (MinLevel=LogLevel.Error), and another for all log entries (MinLevel=LogLevel.Debug). This approach is not suitable if you need to have a separate file that contains log messages only for the concrete category (say, only ERROR or only DEBUG) - if you explain your purpose, I'll try to propose a solution.

CDboyOne commented 2 years ago

Thanks for your response, but I think you are misunderstanding. A category is like Microsoft.AspNetCore or something else, not LogLevel.

VitaliyMF commented 2 years ago

A category is like Microsoft.AspNetCore or something else

that's even easier, as you use the same approach (multiple file loggers) and use message filtering as described here: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/#configure-logging

You can assign a name for each logging provider and configure filtering rules for them separately:

{
  "Logging": {
    "File": {
      "fileLoggingProvider1": {
       "LogLevel": "None",
       "Microsoft.AspNetCore": "Trace" 
      }
    }
  }
}
CDboyOne commented 2 years ago

Thanks for your answer, but I am a little confused about your example. I tried it in my project but it did not produce any log.

Can you give a little example that I can reproduce? Just seperate Microsoft.Hosting and Microsoft.AspNetCore into two files.

By the way, I read the microsoft docs and tried this, although it produces same logs for every file:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "MicrosoftLogging": {
    "File": {
      "LogLevel": {
        "Default": "None",
        "Microsoft": "Information",
        "Microsoft.AspNetCore": "Warning"
      },
      "Path": "Logs/{0:yyyy}-{0:MM}/{0:yyyy}-{0:MM}-{0:dd}-Dzmms.WebApis.Match-msft.log"
    }
  },
  "DzmmsLogging": {
    "File": {
      "LogLevel": {
        "Default": "None",
        "Dzmms": "Information"
      },
      "Path": "Logs/{0:yyyy}-{0:MM}/{0:yyyy}-{0:MM}-{0:dd}-Dzmms.WebApis.Match-dzmms.log"
    }
  },
  "WarningLogging": {
    "File": {
      "LogLevel": {
        "Default": "Warning"
      },
      "Path": "Logs/{0:yyyy}-{0:MM}/{0:yyyy}-{0:MM}-{0:dd}-Dzmms.WebApis.Match-warning.log"
    }
  },
}

Then in C# code:

static string FormatStringWithDateTime(string path) => string.Format(path, DateTime.Now);

services.AddLogging(logbuilder =>
{
    logbuilder.AddFile(configuration.GetSection("MicrosoftLogging"),
        fileLoggerOpts =>
        {
            fileLoggerOpts.FormatLogFileName = FormatStringWithDateTime;
        });
});
services.AddLogging(logbuilder =>
{
    logbuilder.AddFile(configuration.GetSection("DzmmsLogging"),
        fileLoggerOpts =>
        {
            fileLoggerOpts.FormatLogFileName = FormatStringWithDateTime;
        });
});
services.AddLogging(logbuilder =>
{
    logbuilder.AddFile(configuration.GetSection("WarningLogging"),
        fileLoggerOpts =>
        {
            fileLoggerOpts.FormatLogFileName = FormatStringWithDateTime;
        });
});

I will appreciate very much if you could provide me with an example.

VitaliyMF commented 2 years ago

Sorry for confusing you, it seems I pointed you to a wrong direction. I though it is possible to use this:

Settings in Logging.{PROVIDER NAME}.LogLevel override settings in Logging.LogLevel, where the {PROVIDER NAME} placeholder is the provider name.

so idea was to use different provider names -- but it seems, it is not possible to do that as provider's name cannot be provided dynamically, say, in ILoggingFactory.AddProvider method.

For now it is not possible to achieve what you want with FileLoggingProvider. Possible solution might be adding smth like Func<LogMessage, bool> FileLoggerOptions.FilterLogEntry -- in this way you'll able to use a delegate to define any custom filtering logic.

CDboyOne commented 2 years ago

It seems that there is no Func<LogMessage, bool> FileLoggerOptions.FilterLogEntry. There is only a Func<LogMessage, string> FileLoggerOptions.FormatLogEntry, which cannot be used for this purpose.

Maybe it is possible to add a delegate which also takes string category name or LogMessage log message as input, and outputs the formatted filename?

VitaliyMF commented 2 years ago

It seems that there is no Func<LogMessage, bool> FileLoggerOptions.FilterLogEntry.

This might be a possible solution, without any changes in the code it is not possible to achieve what you asked for. If you know how to implement that your PR is welcome, or I can add it when I'll have a bit of free time.

Regarding

Maybe it is possible to add a delegate which also takes string category name or LogMessage log message as input, and outputs the formatted filename?

File name cannot depend on the log name (category), this will lead to open/close files each time when category changes. This is deadly inefficient and will make FileLoggerProvider non-suitable for production use.

VitaliyMF commented 2 years ago

I've added FileLoggerOptions.FilterLogEntry (shipped in 1.1.5). How to use: https://github.com/nreco/logging#custom-log-entry-filtering

Now you can have 2 (or more) file loggers and separate log entries between them with your own filtering predicate.

CDboyOne commented 2 years ago

My hero, thank you very much.