Skip to content

Commit

Permalink
Merge pull request serilog#761 from nblumhardt/f-leveloverride
Browse files Browse the repository at this point in the history
MinimumLevel.Override()
  • Loading branch information
nblumhardt committed May 31, 2016
2 parents b229889 + cbfadba commit 2aed1d8
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 37 deletions.
45 changes: 43 additions & 2 deletions src/Serilog/Configuration/LoggerMinimumLevelConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,18 @@ public class LoggerMinimumLevelConfiguration
readonly LoggerConfiguration _loggerConfiguration;
readonly Action<LogEventLevel> _setMinimum;
readonly Action<LoggingLevelSwitch> _setLevelSwitch;
readonly Action<string, LoggingLevelSwitch> _addOverride;

internal LoggerMinimumLevelConfiguration(LoggerConfiguration loggerConfiguration, Action<LogEventLevel> setMinimum, Action<LoggingLevelSwitch> setLevelSwitch)
internal LoggerMinimumLevelConfiguration(LoggerConfiguration loggerConfiguration, Action<LogEventLevel> setMinimum,
Action<LoggingLevelSwitch> setLevelSwitch, Action<string, LoggingLevelSwitch> addOverride)
{
if (loggerConfiguration == null) throw new ArgumentNullException(nameof(loggerConfiguration));
if (setMinimum == null) throw new ArgumentNullException(nameof(setMinimum));
if (addOverride == null) throw new ArgumentNullException(nameof(addOverride));
_loggerConfiguration = loggerConfiguration;
_setMinimum = setMinimum;
_setLevelSwitch = setLevelSwitch;
_addOverride = addOverride;
}

/// <summary>
Expand All @@ -51,7 +55,8 @@ public LoggerConfiguration Is(LogEventLevel minimumLevel)
/// Sets the minimum level to be dynamically controlled by the provided switch.
/// </summary>
/// <param name="levelSwitch">The switch.</param>
/// <returns>Configuration object allowing method chaining.</returns>
/// <returns>Configuration object allowing method chaining.</returns>
// ReSharper disable once UnusedMethodReturnValue.Global
public LoggerConfiguration ControlledBy(LoggingLevelSwitch levelSwitch)
{
if (levelSwitch == null) throw new ArgumentNullException(nameof(levelSwitch));
Expand All @@ -63,6 +68,7 @@ public LoggerConfiguration ControlledBy(LoggingLevelSwitch levelSwitch)
/// Anything and everything you might want to know about
/// a running block of code.
/// </summary>
/// <returns>Configuration object allowing method chaining.</returns>
public LoggerConfiguration Verbose()
{
return Is(LogEventLevel.Verbose);
Expand All @@ -72,6 +78,7 @@ public LoggerConfiguration Verbose()
/// Internal system events that aren't necessarily
/// observable from the outside.
/// </summary>
/// <returns>Configuration object allowing method chaining.</returns>
public LoggerConfiguration Debug()
{
return Is(LogEventLevel.Debug);
Expand All @@ -81,6 +88,7 @@ public LoggerConfiguration Debug()
/// The lifeblood of operational intelligence - things
/// happen.
/// </summary>
/// <returns>Configuration object allowing method chaining.</returns>
public LoggerConfiguration Information()
{
return Is(LogEventLevel.Information);
Expand All @@ -89,6 +97,7 @@ public LoggerConfiguration Information()
/// <summary>
/// Service is degraded or endangered.
/// </summary>
/// <returns>Configuration object allowing method chaining.</returns>
public LoggerConfiguration Warning()
{
return Is(LogEventLevel.Warning);
Expand All @@ -98,6 +107,7 @@ public LoggerConfiguration Warning()
/// Functionality is unavailable, invariants are broken
/// or data is lost.
/// </summary>
/// <returns>Configuration object allowing method chaining.</returns>
public LoggerConfiguration Error()
{
return Is(LogEventLevel.Error);
Expand All @@ -107,9 +117,40 @@ public LoggerConfiguration Error()
/// If you have a pager, it goes off when one of these
/// occurs.
/// </summary>
/// <returns>Configuration object allowing method chaining.</returns>
public LoggerConfiguration Fatal()
{
return Is(LogEventLevel.Fatal);
}

/// <summary>
/// Override the minimum level for events from a specific namespace or type name.
/// </summary>
/// <param name="source">The (partial) namespace or type name to set the override for.</param>
/// <param name="levelSwitch">The switch controlling loggers for matching sources.</param>
/// <returns>Configuration object allowing method chaining.</returns>
public LoggerConfiguration Override(string source, LoggingLevelSwitch levelSwitch)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (levelSwitch == null) throw new ArgumentNullException(nameof(levelSwitch));

var trimmed = source.Trim();
if (trimmed.Length == 0)
throw new ArgumentException("A source name must be provided.", nameof(source));

_addOverride(trimmed, levelSwitch);
return _loggerConfiguration;
}

/// <summary>
/// Override the minimum level for events from a specific namespace or type name.
/// </summary>
/// <param name="source">The (partial) namespace or type name to set the override for.</param>
/// <param name="minimumLevel">The minimum level applied to loggers for matching sources.</param>
/// <returns>Configuration object allowing method chaining.</returns>
public LoggerConfiguration Override(string source, LogEventLevel minimumLevel)
{
return Override(source, new LoggingLevelSwitch(minimumLevel));
}
}
}
81 changes: 81 additions & 0 deletions src/Serilog/Core/LevelOverrideMap.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright 2016 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 System.Collections.Generic;
using System.Linq;
using Serilog.Events;

namespace Serilog.Core
{
class LevelOverrideMap
{
readonly LogEventLevel _defaultMinimumLevel;
readonly LoggingLevelSwitch _defaultLevelSwitch;

struct LevelOverride
{
public LevelOverride(string context, LoggingLevelSwitch levelSwitch)
{
Context = context;
ContextPrefix = context + ".";
LevelSwitch = levelSwitch;
}

public string Context { get; }
public string ContextPrefix { get; }
public LoggingLevelSwitch LevelSwitch { get; }
}

// There are two possible strategies to apply:
// 1. Keep some bookkeeping data to consult when a new context is encountered, and a concurrent dictionary
// for exact matching ~ O(1), but slow and requires fences/locks; or,
// 2. O(n) search over the raw configuration data every time (fast for small sets of overrides).
// This implementation assumes there will only be a few overrides in each application, so chooses (2). This
// is an assumption that's up for debate.
readonly LevelOverride[] _overrides;

public LevelOverrideMap(
IDictionary<string, LoggingLevelSwitch> overrides,
LogEventLevel defaultMinimumLevel,
LoggingLevelSwitch defaultLevelSwitch)
{
if (overrides == null) throw new ArgumentNullException(nameof(overrides));
_defaultLevelSwitch = defaultLevelSwitch;
_defaultMinimumLevel = defaultLevelSwitch != null ? LevelAlias.Minimum : defaultMinimumLevel;

// Descending order means that if we have a match, we're sure about it being the most specific.
_overrides = overrides
.OrderByDescending(o => o.Key)
.Select(o => new LevelOverride(o.Key, o.Value))
.ToArray();
}

public void GetEffectiveLevel(string context, out LogEventLevel minimumLevel, out LoggingLevelSwitch levelSwitch)
{
foreach (var levelOverride in _overrides)
{
if (context.StartsWith(levelOverride.ContextPrefix) || context == levelOverride.Context)
{
minimumLevel = LevelAlias.Minimum;
levelSwitch = levelOverride.LevelSwitch;
return;
}
}

minimumLevel = _defaultMinimumLevel;
levelSwitch = _defaultLevelSwitch;
}
}
}
41 changes: 32 additions & 9 deletions src/Serilog/Core/Logger.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2013-2015 Serilog Contributors
// Copyright 2013-2016 Serilog Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -42,14 +42,16 @@ public sealed class Logger : ILogger, ILogEventSink, IDisposable
// to its lower limit and fall through to the secondary check.
readonly LogEventLevel _minimumLevel;
readonly LoggingLevelSwitch _levelSwitch;
readonly LevelOverrideMap _overrideMap;

internal Logger(
MessageTemplateProcessor messageTemplateProcessor,
LogEventLevel minimumLevel,
ILogEventSink sink,
ILogEventEnricher enricher,
Action dispose = null)
: this(messageTemplateProcessor, minimumLevel, sink, enricher, dispose, null)
Action dispose = null,
LevelOverrideMap overrideMap = null)
: this(messageTemplateProcessor, minimumLevel, sink, enricher, dispose, null, overrideMap)
{
}

Expand All @@ -58,8 +60,9 @@ internal Logger(
LoggingLevelSwitch levelSwitch,
ILogEventSink sink,
ILogEventEnricher enricher,
Action dispose = null)
: this(messageTemplateProcessor, LevelAlias.Minimum, sink, enricher, dispose, levelSwitch)
Action dispose = null,
LevelOverrideMap overrideMap = null)
: this(messageTemplateProcessor, LevelAlias.Minimum, sink, enricher, dispose, levelSwitch, overrideMap)
{
}

Expand All @@ -71,13 +74,15 @@ internal Logger(
ILogEventSink sink,
ILogEventEnricher enricher,
Action dispose = null,
LoggingLevelSwitch levelSwitch = null)
LoggingLevelSwitch levelSwitch = null,
LevelOverrideMap overrideMap = null)
{
_messageTemplateProcessor = messageTemplateProcessor;
_minimumLevel = minimumLevel;
_sink = sink;
_dispose = dispose;
_levelSwitch = levelSwitch;
_overrideMap = overrideMap;
_enricher = enricher;
}

Expand All @@ -97,7 +102,8 @@ public ILogger ForContext(ILogEventEnricher enricher)
this,
enricher,
null,
_levelSwitch);
_levelSwitch,
_overrideMap);
}

/// <summary>
Expand Down Expand Up @@ -133,8 +139,25 @@ public ILogger ForContext(string propertyName, object value, bool destructureObj
// now and the first log event written...
// A future optimization opportunity may be to implement ILogEventEnricher on LogEventProperty to
// remove one more allocation.
return ForContext(new FixedPropertyEnricher(
_messageTemplateProcessor.CreateProperty(propertyName, value, destructureObjects)));
var enricher = new FixedPropertyEnricher(_messageTemplateProcessor.CreateProperty(propertyName, value, destructureObjects));

var minimumLevel = _minimumLevel;
var levelSwitch = _levelSwitch;
if (_overrideMap != null && propertyName == Constants.SourceContextPropertyName)
{
var context = value as string;
if (context != null)
_overrideMap.GetEffectiveLevel(context, out minimumLevel, out levelSwitch);
}

return new Logger(
_messageTemplateProcessor,
minimumLevel,
this,
enricher,
null,
levelSwitch,
_overrideMap);
}

/// <summary>
Expand Down
41 changes: 15 additions & 26 deletions src/Serilog/LoggerConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2013-2015 Serilog Contributors
// Copyright 2013-2016 Serilog Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -34,7 +34,7 @@ public class LoggerConfiguration
readonly List<ILogEventFilter> _filters = new List<ILogEventFilter>();
readonly List<Type> _additionalScalarTypes = new List<Type>();
readonly List<IDestructuringPolicy> _additionalDestructuringPolicies = new List<IDestructuringPolicy>();

readonly Dictionary<string, LoggingLevelSwitch> _overrides = new Dictionary<string, LoggingLevelSwitch>();
LogEventLevel _minimumLevel = LogEventLevel.Information;
LoggingLevelSwitch _levelSwitch;
int _maximumDestructuringDepth = 10;
Expand Down Expand Up @@ -72,32 +72,21 @@ public LoggerMinimumLevelConfiguration MinimumLevel
_minimumLevel = l;
_levelSwitch = null;
},
sw => _levelSwitch = sw);
sw => _levelSwitch = sw,
(s, lls) => _overrides[s] = lls);
}
}

/// <summary>
/// Configures enrichment of <see cref="LogEvent"/>s. Enrichers can add, remove and
/// modify the properties associated with events.
/// </summary>
public LoggerEnrichmentConfiguration Enrich
{
get
{
return new LoggerEnrichmentConfiguration(this, e => _enrichers.Add(e));
}
}
public LoggerEnrichmentConfiguration Enrich => new LoggerEnrichmentConfiguration(this, e => _enrichers.Add(e));

/// <summary>
/// Configures global filtering of <see cref="LogEvent"/>s.
/// </summary>
public LoggerFilterConfiguration Filter
{
get
{
return new LoggerFilterConfiguration(this, f => _filters.Add(f));
}
}
public LoggerFilterConfiguration Filter => new LoggerFilterConfiguration(this, f => _filters.Add(f));

/// <summary>
/// Configures destructuring of message template parameters.
Expand All @@ -117,13 +106,7 @@ public LoggerDestructuringConfiguration Destructure
/// <summary>
/// Apply external settings to the logger configuration.
/// </summary>
public LoggerSettingsConfiguration ReadFrom
{
get
{
return new LoggerSettingsConfiguration(this);
}
}
public LoggerSettingsConfiguration ReadFrom => new LoggerSettingsConfiguration(this);

/// <summary>
/// Create a logger using the configured sinks, enrichers and minimum level.
Expand Down Expand Up @@ -167,9 +150,15 @@ public Logger CreateLogger()
break;
}

LevelOverrideMap overrideMap = null;
if (_overrides.Count != 0)
{
overrideMap = new LevelOverrideMap(_overrides, _minimumLevel, _levelSwitch);
}

return _levelSwitch == null ?
new Logger(processor, _minimumLevel, sink, enricher, dispose) :
new Logger(processor, _levelSwitch, sink, enricher, dispose);
new Logger(processor, _minimumLevel, sink, enricher, dispose, overrideMap) :
new Logger(processor, _levelSwitch, sink, enricher, dispose, overrideMap);
}
}
}
Loading

0 comments on commit 2aed1d8

Please sign in to comment.