diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/CachingMessageTemplateParser.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/CachingMessageTemplateParser.cs new file mode 100644 index 0000000..d47e84f --- /dev/null +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/CachingMessageTemplateParser.cs @@ -0,0 +1,67 @@ +// Copyright (c) Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +using System; +using Serilog.Events; +using Serilog.Parsing; +using System.Collections; + +namespace Serilog.Extensions.Logging +{ + class CachingMessageTemplateParser + { + readonly MessageTemplateParser _innerParser = new MessageTemplateParser(); + + readonly object _templatesLock = new object(); + readonly Hashtable _templates = new Hashtable(); + + const int MaxCacheItems = 1000; + const int MaxCachedTemplateLength = 1024; + + public MessageTemplate Parse(string messageTemplate) + { + if (messageTemplate == null) throw new ArgumentNullException(nameof(messageTemplate)); + + if (messageTemplate.Length > MaxCachedTemplateLength) + return _innerParser.Parse(messageTemplate); + + // ReSharper disable once InconsistentlySynchronizedField + // ignored warning because this is by design + var result = (MessageTemplate)_templates[messageTemplate]; + if (result != null) + return result; + + result = _innerParser.Parse(messageTemplate); + + lock (_templatesLock) + { + // Exceeding MaxCacheItems is *not* the sunny day scenario; all we're doing here is preventing out-of-memory + // conditions when the library is used incorrectly. Correct use (templates, rather than + // direct message strings) should barely, if ever, overflow this cache. + + // Changing workloads through the lifecycle of an app instance mean we can gain some ground by + // potentially dropping templates generated only in startup, or only during specific infrequent + // activities. + + if (_templates.Count == MaxCacheItems) + _templates.Clear(); + + _templates[messageTemplate] = result; + } + + return result; + } + } +} diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs index 77f357a..1b1f895 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs @@ -10,7 +10,6 @@ using FrameworkLogger = Microsoft.Extensions.Logging.ILogger; using System.Reflection; using Serilog.Debugging; -using Serilog.Parsing; namespace Serilog.Extensions.Logging { @@ -19,7 +18,7 @@ class SerilogLogger : FrameworkLogger readonly SerilogLoggerProvider _provider; readonly ILogger _logger; - static readonly MessageTemplateParser MessageTemplateParser = new MessageTemplateParser(); + static readonly CachingMessageTemplateParser MessageTemplateParser = new CachingMessageTemplateParser(); // It's rare to see large event ids, as they are category-specific static readonly LogEventProperty[] LowEventIdValues = Enumerable.Range(0, 48) @@ -172,4 +171,4 @@ internal static LogEventProperty CreateEventIdProperty(EventId eventId) return new LogEventProperty("EventId", new StructureValue(properties)); } } -} \ No newline at end of file +}