I have written a logging framework that uses Log4Net, Nlog and Serilog interchangeably. Every call to the logger, fires an event before the log entry is written. This optionally pushes entries via SignalR to connected web clients.
Before the serilog addition, I used string.Format to get the formatted text. Now with that great destructuring has come great responsibility. string.Format obviously doesn't like {#0} or {data} in the string.
// log the event before engaging with the logger
LogEventBus.Handle(LogLevels.Info, DateTime.Now, msg, args);
if (DiagnosticLevel < level)
return;
_logger.Info(msg, args);
Is there any way to get the serilog generated output, directly as string?
I started writing a memory sink, but that moves away from my centralised event based logging, and completely breaks away from the other libraries I have implemented.
Any suggestions?
You can convert Serilog's message format to a standard .NET format string ({0} etc) like this:
var parser = new MessageTemplateParser();
var template = parser.Parse(templateMessage);
var format = new StringBuilder();
var index = 0;
foreach (var tok in template.Tokens)
{
if (tok is TextToken)
format.Append(tok);
else
format.Append("{" + index++ + "}");
}
var netStyle = format.ToString();
Once you have a standard format string you can pass this through or use string.Format() with it and args.
It's not going to be super-efficient - hooking deeper into the Serilog pipleine (ILogEventEnricher) should be better. As another commenter suggested, it may be better just to embrace a single logging framework here.
Do your logging in two steps.
Write log message to a TextWriter and read the value from the
TextWriter
(https://github.com/serilog/serilog/wiki/Provided-Sinks#textwriter)
Write that already formatted value into the real logger
Whilst this might work, I worry about your architecture here. It all sounds like yuo are creating huge dependencies on Serilog, whilst you are also using several other logging frmeworks. Choose one logging framework OR use really generic features. C# has introduced string interpolation, not as fancy as Serilogs serialization etc, but works. I'd go back to KISS.
Related
.netcore logger supports a text templating feature that is different than string interpolation.
For example, the following call:
_logger.LogInformation("About page visited at {DT}",DateTime.UtcNow.ToLongTimeString());
... replaces {DT} with a date.
Is this templating capability a special feature in the .netcore logging or a new C# feature?
If it is only implemented in .netcore logging, can I re-use it in my code string processing operation?
Update 1
You cannot use the string with text template like what logging do.
You could use String.Format like below as a alternative way:
string s = string. Format("About page visited at {0}", DateTime.UtcNow.ToLongTimeString());
Reference: get started with the String.Format method
Or you can use in this way:
var time = DateTime.UtcNow.ToLongTimeString();
string s = $"About page visited at {time}";
As the title suggested, is it possible to print out info for web API? I understand there is the option of logging but I am just trying to look for a simple print. I tried console.writeline() but my WEB API do not have a console.
I publish the API to an IIS server, and I would call the API for various functions through a webpage. However, there is no logging setup yet therefore I am trying to find a quick and easy way to print info such as the value of a variable etc. For instance, I would console.log() when I do a quick troubleshoot on my webpage, how can I achieve this for WEB API?
There are a couple of options.
If you use a typical logging framework like Log4Net or Serilog or similar, you can easily write to files. This is easy to set up and recommended.
If you need it now, and are using windows, you could use the event log to write data to it.
See: https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.eventlog?view=dotnet-plat-ext-6.0
// Create an EventLog instance and assign its source.
EventLog myLog = new EventLog();
myLog.Source = "MySource";
// Write an informational entry to the event log.
myLog.WriteEntry("Writing to event log.");
And look for it in the windows event log.
See this on how to access it.
I just noticed writing to a txtfile would be the easiest for me, if I were to avoid setting up logging, basically I will just call as something below when needed.
private void fakeLogging (string data)
{
string logpath = #"C:\path\to\file.txt";
if (!System.IO.File.Exists(logpath))
{
FileStream fs = System.IO.File.Create(logpath);
fs.Close();
}
System.IO.File.AppendAllText(logpath, DateTime.Now + " " + data + Environment.NewLine);
}
Recently I focused in Serilog to point out a templated path based on the current Date of every LogEvent.
After figuring how to implement this, I finally resolve the path on the fly by using the Date field into LogEvent by using Serilog.Sinks.Map, such as shown below:
return new LoggerConfiguration().WriteTo
.Map(
// Log key
(LogEvent le) => le.Timestamp.Date,
// Log Action
(DateTime date, LoggerSinkConfiguration lc) =>
{
string path = GetFilesPath(date, logName);
lc.File(path);
}
);
public string GetFilePath(DateTime date, string logName) =>
Path.Combine("./Logs", $"{date:yyyy-MM-dd}", $"{logName}.log");
With this, I achieved my goal: writing logs with in a sub folder based on the Date.
The issue is, since Serilog does not know that the pointing path changed, it does not close or dispose the file stream as expected. So, my application leaves files opened day to day, ad infinitum.
It'd be great if someone has faced this approach, to manually close the stream, or if Serilog API exposes somehow automatically close those streams.
Btw, I am using
Serilog 2.9.0
Serilog.Sinks.File 4.1.0
Serilog.Sinks.Map 1.0.1
Edit 05/06/2020 for those reading this afterwards.
Keying every single log event by the Timestamp is a bad idea. By doing so, we are in fact adding an entry per log event (supposing that no events are being emitted at the same time, for simplicity).
Even if we specify the sinkMapCountLimit to 0, which in theory won't keep any event in our map, if that event is configured to write to file (specially with the RollingFile sink), those sinks won't be disposed nor erased from memory.
So, the chunk of code above is leaking memory (and pretty fast).
The Map.Sink documentation warns about this, indeed.
...but isn't suitable when the set of possible key values is open-ended.
Serilog.Sinks.Map accepts a parameter sinkMapCountLimit to control this:
return new LoggerConfiguration().WriteTo
.Map(
// Log key
(LogEvent le) => le.Timestamp.Date,
// Log Action
(DateTime date, LoggerSinkConfiguration lc) =>
{
string path = GetFilesPath(date, logName);
lc.File(path);
},
sinkMapCountLimit: 5
);
I am trying to understand the logging output of dotnet run for an ASP.NET Core project. There are many places showing a full type name followed by what appears to be the indexer syntax.
This page explains how array types are represented, but in that case there is no index.
Console.WriteLine(new string[100]); shows:System.String[]
This is an actual dotnet run output:info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[58]
How to interpret the previous text? What is 58?
Is it a general C# string representation? What code construct would output something like that?
In the example provided, there are three components:
info, which is the log level.
Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager, which is the log category.
58, which is the log event ID.
ASP.NET Core uses ILogger and ILogger<T> for logging, using calls such as:
logger.LogInformation(...);
The example log message you've shown is from a console provider, which has its own rules about how to format the message. By default, this starts with a header line of level: category[eventID], as I've shown.
As a crude example, you might imagine the following code being used to generate the final message:
var logLevel = "info";
var logCategory = "Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager";
var logEventId = 58;
Console.Writeline($"{logLevel}: {logCategory}[{logEventId}]");
My question is related with the C# implementation of the google protocol buffers (protobuf-csharp-port, by jon skeet, great job!)
I am experiencing troubles with the extensions: let's say I wrote:
"transport_file.proto" with a "transport message" and some code to
deal with it "code_old".
and I wrote an extension of the transport message on
"Mytransport.proto" file, and new code to read it "code_new".
I'm trying to read a new message (from MyTransport.proto) with the code_old expecting to ignore the extension, but I get an exception in the merge method from TextFormat: "transport" has no field named "whatever_new_field"
Transport.Builder myAppConfigB = new Transport.Builder();
System.IO.StreamReader fich = System.IO.File.OpenText("protocolBus.App.cfg");
TextFormat.Merge(fich.ReadToEnd(),myAppConfigB);
fich.Close();
new extended file looks like:
...
Transport
{
TransportName: "K6Server_0"
DllImport: "protocolBus.Transports.CentralServer"
TransportClass: "K6Server"
K6ServerParams
{
K6Server { host: "85.51.11.23" port: 40069 }
Service: "TZinTalk"
...
}
}
...
while the old one, not extended:
...
Transport
{
TransportName: "K6Server_0"
DllImport: "Default"
TransportClass: "Multicast"
}
...
The whole idea is to use the text based protocol buffer as a config file in which I write some params, and based on one of those I load and assembly (which will read the whole message with the new extension (params to initialize the object).
Any idea? (it is a desperate question :D )
I'm using MSVC# 2008Express edition, protobuf-csharp-port version 0.9.1 (someday I'll upgrade everything).
THANKS in advance.
I'm working on a non centrilized Publish-Subscribe framework of messages (for any written message in a proto file I auto create a Publish and a Subscriber class) with different transports. By the default I use multicast, but broadcast and a "UDP star" are also included. I let the extension mechanism to let people add new transports with its owm config params that should be read by my main code_old (just to load the assembly) and let the new transport (.dll) read it again (fully).
Curious? the previous, almost functional, version is in http://protocolbus.casessite.org
Update 1
Extended types in text format are enclosed in brackets (good to know, I was not aware of it :D ) so I should have written:
[K6ServerParams]
{
K6Server { host: "85.51.11.23" port: 40069 }
Service: "TZinTalk"
...
}
Protocol buffers are designed to be backwards and forwards compatible when using their binary format, but certainly the current code doesn't expect to parse the text format with unknown fields. It could potentially be changed to do that, but I'd want to check with the Java code to try to retain parity with that.
Is there any reason you're not using the binary representation to start with? That's the normal intended usage, and the one where the vast majority of the work has gone in. (Having said which, it all seems a bit of a blur after this long away from the code...)