How to use Serilog to write file for each client? - c#

I am making .Net core application and use Serilog for logging. Now I want to use Serilog to write a log for each clients who already logged in.
I expected the application gonna have seperate logger
Global logger: write server log
Client loggers: each client gonna have their own logger to write log file.
From what I tried to do the application do generate the txt file. However, it doesn't write anything to it.
Program.cs
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder
.UseIISIntegration()
.UseSerilog((hostingcontext, loggerConfiguration) => loggerConfiguration.ReadFrom.Configuration(hostingcontext.Configuration))
.UseStartup<Startup>();
});
}
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<LoggerManager>();
}
...MORE CODE...
}
LoggerManager.cs
public class LoggerManager
{
public static Dictionary<string, ILogger> loggerDict = new Dictionary<string, ILogger>();
public void CreateUserLogger(string username, EnumType userType)
{
var logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.WriteTo.Async(a => a.File(#$"Log/{userType}/{username}.txt",
buffered:true,
rollingInterval:RollingInterval.Day,
retainedFileCountLimit:90))
.CreateLogger();
loggerDict.TryAdd(cheID, logger);
}
public void WriteInfoLog (string username, string message)
{
loggerDict.TryGetValue(username, out ILogger logger);
logger.Information(message);
}
}
HomeController.cs
class HomeController {
private readonly LoggerManager _loggerManager;
public HomeController(LoggerManager loggerManager)
{
_loggerManager = loggerManager;
}
public IActionResult Index() {
_loggerManager.WriteInfoLog(authenticatedUser.CHEID, "HELLO HOW ARE U TODAY");
return View();
}
}
appsettings.json
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"System": "Warning",
"Microsoft": "Warning"
}
},
"WriteTo": [
{
"Name": "Async",
"Args": {
"configure": [
{
"Name": "File",
"Args": {
"path": "Logs/Server/serverlog.txt",
"rollingInterval": "Day",
"retainedFileCountLimit": 7,
"buffered": true
}
}
]
}
}
]
}

I randomly found the "sinks-map". This sinks definitely satisfy my requirement.
https://github.com/serilog/serilog-sinks-map

Related

.NET Core 6 Console App hosted service not starting as Windows Service?

I got a .NET 6.0 Core Console Application that starts a service and log some data with Serilog which works good. But after installing it as a Windows Service and hitting start the services seems to start but I get no log data anymore? What am I missing?
Start up :
public static async Task Main(string[] args)
{
Log.Logger = new LoggerConfiguration().DefaultLoggerSetup<Program>();
var microsoftLogger = new SerilogLoggerFactory(Log.Logger).CreateLogger<IProgram>();
var serviceName = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
Log.Information("Starting {serviceName} version {version}.", serviceName, System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString());
Log.Information("{#serviceName} microservice loading MySettings.json.", serviceName);
var configurationBuilder = new ConfigurationBuilder().AddJsonFile("MySettings.json").Build();
var MySettings = configurationBuilder.Get<MySettings>();
Log.Information("{#serviceName} microservice loading appsettings.json.", serviceName);
configurationBuilder = new ConfigurationBuilder().AddJsonFile("appSettings.json").Build();
var appSettings = configurationBuilder.Get<AppSettings>();
var sqlConnectionString = appSettings.ConnectionString;
Log.Information("{#serviceName} building and starting.", serviceName);
CreateHostBuilder(args, configurationBuilder, appSettings, MySettings, sqlConnectionString).Build().Run();
Log.Information("{#serviceName} microservice Built and stated.", serviceName);
}
Method to build the host :
public static IHostBuilder CreateHostBuilder(string[] args,
IConfiguration configurationBuilder,
AppSettings appSettings,
MySettings mySettings,
string sqlConnectionString) =>
Host.CreateDefaultBuilder(args)
.UseCloudMQ(context => context.UseSettings(appSettings.MQSettings))
.UseSerilog((context, services, configuration) => configuration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.Enrich.FromLogContext())
.UseWindowsService()
.ConfigureServices((hostContext, services) =>
{
services
.AddTransient<ConnectorConfig>(s => appSettings.ConnectorConfig)
.AddTransient<IMyService, MyService>()
.AddTransient<IMyController, MyController>()
.AddTransient<IMySettings>(s => MySettings)
.AddTransient<IMyService, MyService>()
.AddSingleton<IInputOuputLogger>(new InputOuputLogger(configurationBuilder))
.AddDbContext<AppDbContext>(options => options.UseSqlServer(sqlConnectionString, opt => opt.UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery)))
.AddHostedService<MyService>();
});
Simple hosted service :
public class MyService : IHostedService, IMyAnestesiService
{
private readonly ILogger<MyService> _logger;
public MyService(ILogger<MyService> logger)
{
_logger = logger;
_logger.LogInformation("{#serviceName} microservice started.", GetType().Name);
}
public Task StartAsync(CancellationToken cancellationToken)
{
return Task.FromResult(true);
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.FromResult(true);
}
public void Run()
{
_logger.LogInformation("MyService started.");
}
}
Edit : When running the service in CMD the log in displayed in the CMD but its also saved to file with this :
"serilog": {
"Using": [
"Serilog",
"Serilog.Sinks.File",
"Serilog.Sinks.Console"
],
"MinimumLevel": {
"Default": "Verbose",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"Enrich": [ "FromLogContext", "WithMachineName", "WithProcessId" ],
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Timestamp:HH:mm:ss.fff} [{Level}] {SourceContext} {Message}{NewLine}{Exception}",
"theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console"
}
},
{
"Name": "File",
"Args": {
"path": "C:\\DEVSTUFF\\log.txt",
"outputTemplate": "{Timestamp:G} {SourceContext} [{Level}] {Message}{NewLine:1}{Exception:1}",
"formatter": "Serilog.Formatting.Json.JsonFormatter, Serilog",
"fileSizeLimitBytes": 1000000,
"rollOnFileSizeLimit": "true",
"shared": "true",
"flushToDiskInterval": 3
}
}
]
}
So even if I canĀ“t see the log in the CMD while running it as a Windows Services I should at least get log rows in file like before.
Where are you writing logs in the Windows Service scenario?
For the console application, it can write logs to the Terminal which is not possible for the windows service scenarios. You need to write somewhere like a file, or database.
You can have a look at this link

ILogger does not write to console using serilog

I'm trying to use serligo as my main logging utilitie but I can't seem to get it to work correctly.
In the Program.cs file, I configure the logger and also log a startup message, this message gets outputted to the console.
I also have, in one of my controllers, an ILogger<HomeController> instance that logs information when a certain action is called. These logs are not outputted to the console, and I cannot understand why.
Startup.cs
public class Program
{
public static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger();
try
{
Log.Information("Starting up version {version}", Defaults.Version); // WORKS
CreateHostBuilder(args).Build().Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Application start-up failed");
}
finally
{
Log.CloseAndFlush();
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration.ReadFrom.Configuration(hostingContext.Configuration))
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Controller
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
_logger.LogInformation("Home/Index"); //Does not work
return View();
}
}
appsettings.json
"Serilog": {
"Using": [ "Serilog.Sinks.Console" ],
"MinimumLevel": {
"Default": "Verbose",
"Override": {
"Microsoft": "Warning",
"Microsoft.AspNetCore": "Warning",
"System": "Error"
}
},
"WriteTo": [
{
"Name": "Async",
"Args": {
"configure": [
{
"Name": "Console",
"Args": {
"theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console",
"formatter": "Serilog.Formatting.Compact.RenderedCompactJsonFormatter, Serilog.Formatting.Compact"
}
}
]
}
}
]
},
.UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration.ReadFrom.Configuration(hostingContext.Configuration))
The issue relates the above code, in this part of code, you should also configure it to write to the console.
Modify the code as below:
.UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration.ReadFrom.Configuration(hostingContext.Configuration).WriteTo.Console())
Then, the output like this:

.NET Core. Logs not sent to Loggly, while using Serilog

I have a small ASP.NET Core 3.1 project that should execute the Odata request and then log either info or exception into the Loggly. I'm using Serilog for this.
But for some reason, it only sends the very first message from the Program.cs only (Log.Debug($"DEBUG: Starting at:{DateTime.Now}");), but does not send anything other than that. While it works fine for the Console or File logging.
I have tried to fake an exception to trigger the Error logging. But nothing works (in the Loggly).
Any suggestions on why is that and how to make it work properly?
Here is my code:
Program.cs:
public class Program
{
public static void Main(string[] args)
{
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.CreateLogger();
try
{
Log.Debug($"DEBUG: Starting at:{DateTime.Now}");
CreateHostBuilder(args).Build().Run();
Log.Debug($"DEBUG: Host created");
}
catch (Exception ex)
{
Log.Error("Oops something failed!");
throw new Exception(ex.Message);
}
finally
{
Log.CloseAndFlush();
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.UseSerilog();
}
Startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDbContext<OdataTestObjectContext>(options =>
{
options.UseSqlServer(Configuration["ConnectionStrings:Database"]);
});
services.AddControllers().AddOData(opt =>
opt.Filter().Expand().Select().OrderBy().Count().SetMaxTop(100)
.AddRouteComponents("odata", GetEdmModel()));
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "OdataTest", Version = "v1" });
c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
});
var appSettings = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
var serilogConfig = new LoggerConfiguration().ReadFrom.Configuration(appSettings);
services.AddLogging(loggingBuilder => loggingBuilder.AddSerilog(serilogConfig.CreateLogger(), true));
services.AddMvc(opts => { opts.Filters.Add(new AutoLogAttribute()); });
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger(c =>
{
c.RouteTemplate = "/swagger/{documentName}/swagger.json";
});
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "OdataTest v1"));
}
app.UseHttpsRedirection();
////Send "~/$odata" to debug routing if enable the following middleware
app.UseODataRouteDebug();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
var entitySet = builder.EntitySet<TestObject>("TestObjects");
entitySet.EntityType.HasKey(entity => entity.Id);
return builder.GetEdmModel();
}
}
Controller.cs
[ApiController]
[Route("OData")]
public class TestController : ODataController
{
private readonly OdataTestObjectContext _context;
private readonly ILogger<TestController> _logger;
public TestController(ILogger<TestController> logger, OdataTestObjectContext context)
{
_context = context;
_logger = logger;
}
[ApiExplorerSettings(IgnoreApi = true)]
[EnableQuery]
public IActionResult Get()
{
try
{
return Ok(_context.TestObjects);
}
catch (Exception ex)
{
_logger.LogError("Error happened for Default query! - {0}", ex.Message);
throw new InvalidOperationException(ex.Message);
}
}
[HttpGet("Top({top})")]
[EnableQuery]
public IEnumerable<TestObject> GetTop(int top)
{
return _context.TestObjects.OrderByDescending(x => x.Id).Take(top);
}
[HttpGet("Enumerable")]
[EnableQuery]
public IEnumerable<TestObject> Enumerable()
{
try
{
_logger.LogDebug("Executing ENUMERABLE now.");
//string a = "asd";
//int s = Convert.ToInt32(a);
var results = _context.SaleInvoicesAndProducts.AsEnumerable();
int z = 1;
if (results.Any())
z = 0;
int i = 10 / z;
return results;
}
catch (Exception ex)
{
_logger.LogError("Error happened for Enumerable query! - {0}", ex.Message);
throw new Exception();
}
}
AutoLogAttribute.cs (this just outputs logs with some request's parameters):
public class AutoLogAttribute : TypeFilterAttribute
{
public AutoLogAttribute() : base(typeof(AutoLogActionFilterImpl))
{
}
private class AutoLogActionFilterImpl : IActionFilter
{
private readonly ILogger<AutoLogAttribute> _logger;
public AutoLogActionFilterImpl(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<AutoLogAttribute>();
}
public void OnActionExecuting(ActionExecutingContext context)
{
// perform some business logic work
_logger.LogInformation($"queryString: {context.HttpContext.Request.QueryString}");
_logger.LogDebug($"queryString (Debug): {context.HttpContext.Request.QueryString}");
}
public void OnActionExecuted(ActionExecutedContext context)
{
//TODO: log body content and response as well
_logger.LogInformation($"host: {context.HttpContext.Request.Host}");
_logger.LogInformation($"patheAndQuery: {context.HttpContext.Request.GetEncodedPathAndQuery()}");
_logger.LogDebug($"path (Debug): {context.HttpContext.Request.Path}");
_logger.LogDebug($"host (Debug): {context.HttpContext.Request.Host}");
_logger.LogDebug($"patheAndQuery (Debug): {context.HttpContext.Request.GetEncodedPathAndQuery()}");
}
}
}
appsetting.json
{
"Serilog": {
"Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File", "Serilog.Sinks.Loggly" ],
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Debug",
"System": "Debug"
}
},
"Enrich": [ "FromLogContext", "WithMachineName", "WithProcessId", "WithThreadId" ],
"WriteTo": [
{ "Name": "Console" },
{
"Name": "File",
"Args": { "path": "C:\\ODATA\\log.json" }
},
{
"Name": "Loggly",
"Args": {
"customerToken": "mytoken",
"tags": "mytest"
}
}
],
},
"ConnectionStrings": {
"Database": "Server=(localdb)\\MSSQLLocalDB;Initial Catalog=MyTestDB;Integrated Security=true;MultiSubnetFailover=True;MultipleActiveResultSets=True;"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
You can try to clear providers:
Host
****
.ConfigureLogging(logging =>
{
logging.ClearProviders();
})
.UseSerilog();
Also, if you have configured logging on Host, you don't need to put the additional configuration into Startup.cs

Search After Usage API not returning logs

I am wanting to return the logs that contain "level": "error" from elasticsearch in a asp.net core web api application using NEST. I looked into the search_after api Search After API and looked into another resource for Paginate Search After. On the Kibana CLI, I wrote the following:
GET elastic-search-app-log*/_search
{
"size": 3000,
"query": {
"match": {
"level": "Error"
}
},
"search_after": [3000],
"sort":[
{"#timestamp": "asc"}
]
}
I just set the size to something random along and for it just to search after each 3000 indice.
So on the .net side of it, I attempted to translate it as so:
ESFieldsController
private readonly IElasticClient _elasticClient;
public ESFieldsController(IElasticClient elasticClient)
{
_elasticClient = elasticClient;
}
[HttpGet]
public async Task<ESFields> Get()
{
var response = await _elasticClient.SearchAsync<ESFields>(s => s
.Index("elastic-search-app-logs*")
.Size(3000)
.Query(q => q.Match(m => m.Field(f => f.Level == "error")))
.SearchAfter(3000)
.Sort(srt => srt
.Ascending(p => p.TimeStamp)));
Console.WriteLine(response);
return response?.Documents?.FirstOrDefault();
}
ESFields
namespace ESPractice.Models
{
public class ESFields
{
public String Level { get; set; }
public DateTime TimeStamp { get; set; }
}
}
However, when I run the application and swagger comes up, I try to execute the get cmd to see if it works but it does not return the logs that contain "level": "error". Is there something I am doing incorrectly with the translation?
Additional info:
startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "ESPractice", Version = "v1" });
});
// create a new node instance
var node = new Uri("http://localhost:9200");
// settings instance for the node
var settings = new ConnectionSettings(node);
services.AddSingleton<IElasticClient>(new ElasticClient(settings));
}
Everything below is from a separate application where I am writing the logs to elasticsearch:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog((context, configuration) =>
{
configuration.Enrich.FromLogContext()
.Enrich.WithMachineName()
.WriteTo.Console()
.WriteTo.Elasticsearch(
new ElasticsearchSinkOptions(new Uri(context.Configuration["ElasticConfiguration:Uri"]))
{
IndexFormat = $"{context.Configuration["ApplicationName"]}-logs-{context.HostingEnvironment.EnvironmentName?.ToLower().Replace(".", "-")}-{DateTime.UtcNow:yyyy-MM}",
AutoRegisterTemplate = true,
})
.Enrich.WithProperty("Environment", context.HostingEnvironment.EnvironmentName)
.ReadFrom.Configuration(context.Configuration);
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
appsettings.json
{
"ApplicationName": "elastic-search-app",
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Information",
"System": "Warning"
}
}
},
"ElasticConfiguration": {
"Uri": "http://localhost:9200"
},
"AllowedHosts": "*"
}

Read appsettings.json - Fields remain null

Think I have a problem with the startup.cs as I do not get any values from my <IOption> config
So.. We have our appsettings.json
"Config": {
"ApplicationName": "some name",
"ConnectionString": "someconstring",
"Version": "1.0.0"
},
Here we have our model
public class Config
{
public string ApplicationName { get; set; }
public string ConnectionString { get; set; }
public string Version { get; set; }
}
The startup.cs
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public static IConfiguration Configuration { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// Add functionality to inject IOptions<T>
services.AddOptions();
// Add our Config object so it can be injected
services.Configure<Config>(Configuration);
}
And then in our controller I try to load those data but unfortunately they remain empty.
private IOptions<Config> config;
public CompaniesController(IOptions<Config> config)
{
this.config = config;
}
I've tried to change the startup.cs with something like
services.Configure<Config>(options =>
{
options.ConnectionString = Configuration.GetSection("Config:ConnectionString").Value;
});
but that doesn't seems to work.
Resources I've been using:
https://dzone.com/articles/dynamic-connection-string-in-net-core
https://stackoverflow.com/questions/31453495/how-to-read-appsettings-values-from-json-file-in-asp-net-core
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-2.2
but Obviously I am missing a crucial point here.
edit: I am using ASP.Net Core 2.0
edit2:
the Program.cs
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
The entire Appsettings.json file
{
"Config": {
"ApplicationName": "somename",
"ConnectionString": "someconstring",
"Version": "1.0.0"
},
"Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Warning"
}
},
"Console": {
"LogLevel": {
"Default": "Warning"
}
}
}
}
edit 3:
In my front-end application I import the API like this.
services.Configure<Config>(Configuration);
This line doesn't achieve the desired result because the JSON properties you're looking for are nested under a Config property in your appsettings.json file. To load these values as intended, use GetSection to grab the Config section and pass that into the Configure<TOptions> method:
services.Configure<Config>(Configuration.GetSection("Config"));
services.Configure<Config>(options =>
{
options.ConnectionString = Configuration.GetValue<string>("Config:ConnectionString");
options.ApplicationName = "test";
});
If you want to configure your options more granuarly.

Categories