public void Format(IEnumerable <LogEvent> logEvents, ITextFormatter formatter, TextWriter output)
        {
            if (logEvents == null)
            {
                throw new ArgumentNullException(nameof(logEvents));
            }
            if (output == null)
            {
                throw new ArgumentNullException(nameof(output));
            }

            List <LogEvent> logs = logEvents.ToList();

            if (!logs.Any())
            {
                return;
            }

            var streamsDictionary = new Dictionary <string, LokiContentStream>();

            foreach (LogEvent logEvent in logs)
            {
                var labels = new List <LokiLabel>();

                foreach (LokiLabel globalLabel in this.LogLabelProvider.GetLabels())
                {
                    labels.Add(new LokiLabel(globalLabel.Key, globalLabel.Value));
                }

                var time = logEvent.Timestamp.ToString("o");
                var sb   = new StringBuilder();
                using (var tw = new StringWriter(sb))
                {
                    formatter.Format(logEvent, tw);
                }

                HandleProperty("level", GetLevel(logEvent.Level), labels, sb);
                foreach (KeyValuePair <string, LogEventPropertyValue> property in logEvent.Properties)
                {
                    HandleProperty(property.Key, property.Value.ToString(), labels, sb);
                }

                // Order the labels so they always get the same chunk in loki
                labels = labels.OrderBy(l => l.Key).ToList();
                var key = string.Join(",", labels.Select(l => $"{l.Key}={l.Value}"));
                if (!streamsDictionary.TryGetValue(key, out var stream))
                {
                    streamsDictionary.Add(key, stream = new LokiContentStream());
                    stream.Labels.AddRange(labels);
                }

                // Loki doesn't like \r\n for new line, and we can't guarantee the message doesn't have any
                // in it, so we replace \r\n with \n on the final message
                stream.Entries.Add(new LokiEntry(time, sb.ToString().Replace("\r\n", "\n")));
            }

            if (streamsDictionary.Count > 0)
            {
                var content = new LokiContent
                {
                    Streams = streamsDictionary.Values.ToList()
                };
                output.Write(content.Serialize());
            }
        }
        public void Format(IEnumerable <LogEvent> logEvents, ITextFormatter formatter, TextWriter output)
        {
            if (logEvents == null)
            {
                throw new ArgumentNullException(nameof(logEvents));
            }
            if (output == null)
            {
                throw new ArgumentNullException(nameof(output));
            }

            List <LogEvent> logs = logEvents.ToList();

            if (!logs.Any())
            {
                return;
            }

            var content = new LokiContent();

            foreach (LogEvent logEvent in logs)
            {
                var stream = new LokiContentStream();
                content.Streams.Add(stream);

                stream.Labels.Add(new LokiLabel("level", GetLevel(logEvent.Level)));
                foreach (LokiLabel globalLabel in _globalLabels)
                {
                    stream.Labels.Add(new LokiLabel(globalLabel.Key, globalLabel.Value));
                }

                foreach (KeyValuePair <string, LogEventPropertyValue> property in logEvent.Properties)
                {
                    // Some enrichers pass strings with quotes surrounding the values inside the string,
                    // which results in redundant quotes after serialization and a "bad request" response.
                    // To avoid this, remove all quotes from the value.
                    // We also remove any \r\n newlines and replace with \n new lines to prevent "bad request" responses
                    // We also remove backslashes and replace with forward slashes, Loki doesn't like those either
                    stream.Labels.Add(new LokiLabel(property.Key, property.Value.ToString().Replace("\"", "").Replace("\r\n", "\n").Replace("\\", "/")));
                }

                var time = logEvent.Timestamp.ToString("o");

                var sb = new StringBuilder();
                sb.AppendLine(logEvent.RenderMessage());
                if (logEvent.Exception != null)
                {
                    var e = logEvent.Exception;
                    while (e != null)
                    {
                        sb.AppendLine(e.Message);
                        sb.AppendLine(e.StackTrace);
                        e = e.InnerException;
                    }
                }

                // Loki doesn't like \r\n for new line, and we can't guarantee the message doesn't have any
                // in it, so we replace \r\n with \n on the final message
                // We also flip backslashes to forward slashes, Loki doesn't like those either.
                stream.Entries.Add(new LokiEntry(time, sb.ToString().Replace("\\", "/").Replace("\r\n", "\n")));
            }

            if (content.Streams.Count > 0)
            {
                output.Write(content.Serialize());
            }
        }
        public void Format(IEnumerable <LogEvent> logEvents, ITextFormatter formatter, TextWriter output)
        {
            if (logEvents == null)
            {
                throw new ArgumentNullException(nameof(logEvents));
            }
            if (output == null)
            {
                throw new ArgumentNullException(nameof(output));
            }

            List <LogEvent> logs = logEvents.ToList();

            if (!logs.Any())
            {
                return;
            }

            var content = new LokiContent();

            foreach (LogEvent logEvent in logs)
            {
                var stream = new LokiContentStream();
                content.Streams.Add(stream);

                stream.Labels.Add(new LokiLabel("level", GetLevel(logEvent.Level)));
                foreach (LokiLabel globalLabel in _globalLabels)
                {
                    stream.Labels.Add(new LokiLabel(globalLabel.Key, globalLabel.Value));
                }

                foreach (KeyValuePair <string, LogEventPropertyValue> property in logEvent.Properties)
                {
                    // Some enrichers pass strings with quotes surrounding the values inside the string,
                    // which results in redundant quotes after serialization and a "bad request" response.
                    // To avoid this, remove all quotes from the value.
                    stream.Labels.Add(new LokiLabel(property.Key, property.Value.ToString().Replace("\"", "")));
                }

                var localTime          = DateTime.Now;
                var localTimeAndOffset = new DateTimeOffset(localTime, TimeZoneInfo.Local.GetUtcOffset(localTime));
                var time = localTimeAndOffset.ToString("o");

                var sb = new StringBuilder();
                sb.AppendLine(logEvent.RenderMessage());
                if (logEvent.Exception != null)
                {
                    var e = logEvent.Exception;
                    while (e != null)
                    {
                        sb.AppendLine(e.Message);
                        sb.AppendLine(e.StackTrace);
                        e = e.InnerException;
                    }
                }

                stream.Entries.Add(new LokiEntry(time, sb.ToString()));
            }

            if (content.Streams.Count > 0)
            {
                output.Write(content.Serialize());
            }
        }