internal JsonConsoleLogger()

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();
                }
            }
        }