in src/Google.Cloud.Functions.Hosting/Logging/JsonConsoleLogger.cs [37:134]
internal JsonConsoleLogger(string category, IExternalScopeProvider? scopeProvider, TextWriter console)
: base(category, scopeProvider) => _console = console;
protected override void LogImpl<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, string formattedMessage)
{
string severity = logLevel switch
{
LogLevel.Trace => "DEBUG",
LogLevel.Debug => "DEBUG",
LogLevel.Information => "INFO",
LogLevel.Warning => "WARNING",
LogLevel.Error => "ERROR",
LogLevel.Critical => "CRITICAL",
_ => throw new ArgumentOutOfRangeException(nameof(logLevel))
};
var outputStream = new MemoryStream();
using (var writer = new Utf8JsonWriter(outputStream))
{
writer.WriteStartObject();
writer.WritePropertyName("message");
writer.WriteStringValue(formattedMessage);
writer.WritePropertyName("category");
writer.WriteStringValue(Category);
if (exception != null)
{
writer.WritePropertyName("exception");
writer.WriteStringValue(ToInvariantString(exception));
}
writer.WritePropertyName("severity");
writer.WriteStringValue(severity);
// If we have format params and its more than just the original message add them.
if (state is IEnumerable<KeyValuePair<string, object>> formatParams &&
ContainsFormatParameters(formatParams))
{
writer.WritePropertyName("format_parameters");
writer.WriteStartObject();
foreach (var pair in formatParams)
{
string key = pair.Key;
if (string.IsNullOrEmpty(key))
{
continue;
}
if (char.IsDigit(key[0]))
{
key = "_" + key;
}
writer.WritePropertyName(key);
writer.WriteStringValue(ToInvariantString(pair.Value));
}
writer.WriteEndObject();
}
// Write the scopes as an array property, but only if there are any.
ScopeProvider.ForEachScope(WriteScope, writer);
// If there are no scopes, the write state will still be "object". If
// we've written at least one scope, the write state will be "array".
if (writer.CurrentDepth != NoScopesDepth)
{
writer.WriteEndArray();
}
writer.WriteEndObject();
}
// It's unfortunate that we need to write to a stream, then convert the result
// into a string. We can avoid creating an extra copy of the binary data though,
// as we should always be able to get the underlying buffer.
var buffer = outputStream.GetBuffer();
var text = Encoding.UTF8.GetString(buffer, 0, (int) outputStream.Length);
_console.WriteLine(text);
// Checks that fields is:
// - Non-empty
// - Not just a single entry with a key of "{OriginalFormat}"
// so we can decide whether or not to populate a struct with it.
bool ContainsFormatParameters(IEnumerable<KeyValuePair<string, object>> fields)
{
using (var iterator = fields.GetEnumerator())
{
// No fields? Nothing to format.
if (!iterator.MoveNext())
{
return false;
}
// If the first entry isn't the original format, we definitely want to create a struct
if (iterator.Current.Key != "{OriginalFormat}")
{
return true;
}
// If the first entry *is* the original format, we want to create a struct
// if and only if there's at least one more entry.
return iterator.MoveNext();
}
}
}