Skip to content

Commit aadce3a

Browse files
authored
summary: Log Level Filtering (#1770)
feat: Add support for filtering log events based on a list of log levels so that they are not forwarded to New Relic. Also adds new logging metrics to count the total number of filtered log events (Logging/denied). Refer to our [application logging configuration](https://docs.newrelic.com/docs/apm/agents/net-agent/configuration/net-agent-configuration/#application_logging) documentation for more details. (#1760) (#1761) (#1762) (#1766)
1 parent 6c98ba2 commit aadce3a

File tree

20 files changed

+534
-7
lines changed

20 files changed

+534
-7
lines changed

src/Agent/NewRelic/Agent/Core/Agent.cs

+11-2
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ public void RecordSupportabilityMetric(string metricName, int count)
408408
_agentHealthReporter.ReportSupportabilityCountMetric(metricName, count);
409409
}
410410

411-
public void RecordLogMessage(string frameworkName, object logEvent, Func<object, DateTime> getTimestamp, Func<object, object> getLevel, Func<object, string> getLogMessage, Func<object, Exception> getLogException,Func<object, Dictionary<string, object>> getContextData, string spanId, string traceId)
411+
public void RecordLogMessage(string frameworkName, object logEvent, Func<object, DateTime> getTimestamp, Func<object, object> getLevel, Func<object, string> getLogMessage, Func<object, Exception> getLogException, Func<object, Dictionary<string, object>> getContextData, string spanId, string traceId)
412412
{
413413
_agentHealthReporter.ReportLogForwardingFramework(frameworkName);
414414

@@ -418,6 +418,15 @@ public void RecordLogMessage(string frameworkName, object logEvent, Func<object,
418418
{
419419
var level = getLevel(logEvent).ToString();
420420
normalizedLevel = string.IsNullOrWhiteSpace(level) ? "UNKNOWN" : level.ToUpper();
421+
422+
// LogLevelDenyList is already uppercase, so don't need case-insensitive lookup
423+
if (_configurationService.Configuration.LogLevelDenyList.Contains(normalizedLevel))
424+
{
425+
if (_configurationService.Configuration.LogMetricsCollectorEnabled)
426+
_agentHealthReporter.IncrementLogDeniedCount(normalizedLevel);
427+
428+
return;
429+
}
421430
}
422431

423432
if (_configurationService.Configuration.LogMetricsCollectorEnabled)
@@ -432,7 +441,7 @@ public void RecordLogMessage(string frameworkName, object logEvent, Func<object,
432441

433442
var logMessage = getLogMessage(logEvent);
434443
var logException = getLogException(logEvent);
435-
444+
436445
// exit quickly if the message and exception are missing
437446
if (string.IsNullOrWhiteSpace(logMessage) && logException is null)
438447
{

src/Agent/NewRelic/Agent/Core/AgentHealth/AgentHealthReporter.cs

+23
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class AgentHealthReporter : ConfigurationBasedService, IAgentHealthReport
2929
private readonly IList<RecurringLogData> _recurringLogDatas = new ConcurrentList<RecurringLogData>();
3030
private readonly IDictionary<AgentHealthEvent, InterlockedCounter> _agentHealthEventCounters = new Dictionary<AgentHealthEvent, InterlockedCounter>();
3131
private readonly ConcurrentDictionary<string, InterlockedCounter> _logLinesCountByLevel = new ConcurrentDictionary<string, InterlockedCounter>();
32+
private readonly ConcurrentDictionary<string, InterlockedCounter> _logDeniedCountByLevel = new ConcurrentDictionary<string, InterlockedCounter>();
3233

3334
private PublishMetricDelegate _publishMetricDelegate;
3435
private InterlockedCounter _payloadCreateSuccessCounter;
@@ -583,6 +584,22 @@ public void CollectLoggingMetrics()
583584
_loggingForwardingEnabledWithFrameworksReported[kvp.Key] = true;
584585
}
585586
}
587+
588+
var totalDeniedCount = 0;
589+
foreach (var logLinesDeniedCounter in _logDeniedCountByLevel)
590+
{
591+
if (TryGetCount(logLinesDeniedCounter.Value, out var linesCount))
592+
{
593+
totalDeniedCount += linesCount;
594+
TrySend(_metricBuilder.TryBuildLoggingMetricsDeniedCountBySeverityMetric(logLinesDeniedCounter.Key, linesCount));
595+
}
596+
}
597+
598+
if (totalDeniedCount > 0)
599+
{
600+
TrySend(_metricBuilder.TryBuildLoggingMetricsDeniedCountMetric(totalDeniedCount));
601+
}
602+
586603
}
587604

588605
public void IncrementLogLinesCount(string level)
@@ -591,6 +608,12 @@ public void IncrementLogLinesCount(string level)
591608
_logLinesCountByLevel[level].Increment();
592609
}
593610

611+
public void IncrementLogDeniedCount(string level)
612+
{
613+
_logDeniedCountByLevel.TryAdd(level, new InterlockedCounter());
614+
_logDeniedCountByLevel[level].Increment();
615+
}
616+
594617
public void ReportLoggingEventCollected() => TrySend(_metricBuilder.TryBuildSupportabilityLoggingEventsCollectedMetric());
595618

596619
public void ReportLoggingEventsSent(int count) => TrySend(_metricBuilder.TryBuildSupportabilityLoggingEventsSentMetric(count));

src/Agent/NewRelic/Agent/Core/AgentHealth/IAgentHealthReporter.cs

+1
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ public interface IAgentHealthReporter : IOutOfBandMetricSource
142142
void ReportSupportabilityDataUsage(string api, string apiArea, long dataSent, long dataReceived);
143143

144144
void IncrementLogLinesCount(string logLevel);
145+
void IncrementLogDeniedCount(string logLevel);
145146
void ReportLoggingEventCollected();
146147
void ReportLoggingEventsSent(int count);
147148
void ReportLoggingEventsDropped(int droppedCount);

src/Agent/NewRelic/Agent/Core/Config/Configuration.cs

+15
Original file line numberDiff line numberDiff line change
@@ -5035,6 +5035,8 @@ public partial class configurationApplicationLoggingForwarding
50355035

50365036
private int maxSamplesStoredField;
50375037

5038+
private string logLevelDenyListField;
5039+
50385040
/// <summary>
50395041
/// configurationApplicationLoggingForwarding class constructor
50405042
/// </summary>
@@ -5085,6 +5087,19 @@ public int maxSamplesStored
50855087
}
50865088
}
50875089

5090+
[System.Xml.Serialization.XmlAttributeAttribute()]
5091+
public string logLevelDenyList
5092+
{
5093+
get
5094+
{
5095+
return this.logLevelDenyListField;
5096+
}
5097+
set
5098+
{
5099+
this.logLevelDenyListField = value;
5100+
}
5101+
}
5102+
50885103
#region Clone method
50895104
/// <summary>
50905105
/// Create a clone of this configurationApplicationLoggingForwarding object

src/Agent/NewRelic/Agent/Core/Config/Configuration.xsd

+9
Original file line numberDiff line numberDiff line change
@@ -1655,6 +1655,15 @@
16551655
</xs:documentation>
16561656
</xs:annotation>
16571657
</xs:attribute>
1658+
1659+
<xs:attribute name="logLevelDenyList" type="xs:string" use="optional">
1660+
<xs:annotation>
1661+
<xs:documentation>
1662+
A comma-separated, case-insensitive, list of log level names from the selected logging framework that should be ignored and not sent up to New Relic.
1663+
</xs:documentation>
1664+
</xs:annotation>
1665+
</xs:attribute>
1666+
16581667
</xs:complexType>
16591668
</xs:element>
16601669
<xs:element name="localDecorating" minOccurs="0" maxOccurs="1">

src/Agent/NewRelic/Agent/Core/Configuration/DefaultConfiguration.cs

+25
Original file line numberDiff line numberDiff line change
@@ -1990,6 +1990,31 @@ public virtual bool LogDecoratorEnabled
19901990
}
19911991
}
19921992

1993+
private HashSet<string> _logLevelDenyList;
1994+
public virtual HashSet<string> LogLevelDenyList
1995+
{
1996+
get
1997+
{
1998+
if (_logLevelDenyList == null)
1999+
{
2000+
_logLevelDenyList = new HashSet<string>(
2001+
EnvironmentOverrides(_localConfiguration.applicationLogging.forwarding.logLevelDenyList,
2002+
"NEW_RELIC_APPLICATION_LOGGING_FORWARDING_LOG_LEVEL_DENYLIST")
2003+
?.Split(new[] { StringSeparators.CommaChar, ' ' }, StringSplitOptions.RemoveEmptyEntries)
2004+
.Select(s => s.ToUpper())
2005+
?? Enumerable.Empty<string>());
2006+
2007+
if (_logLevelDenyList.Count > 0)
2008+
{
2009+
var logLevels = string.Join(",", _logLevelDenyList);
2010+
Log.Info($"Log Level Filtering is enabled for the following levels: {logLevels}");
2011+
}
2012+
}
2013+
2014+
return _logLevelDenyList;
2015+
}
2016+
}
2017+
19932018
#endregion
19942019

19952020
public virtual bool AppDomainCachingDisabled

src/Agent/NewRelic/Agent/Core/Configuration/ReportedConfiguration.cs

+3
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,9 @@ public ReportedConfiguration(IConfiguration configuration)
593593
[JsonProperty("application_logging.forwarding.max_samples_stored")]
594594
public int LogEventsMaxSamplesStored => _configuration.LogEventsMaxSamplesStored;
595595

596+
[JsonProperty("application_logging.forwarding.log_level_denylist")]
597+
public HashSet<string> LogLevelDenyList => _configuration.LogLevelDenyList;
598+
596599
[JsonProperty("application_logging.harvest_cycle")]
597600
public TimeSpan LogEventsHarvestCycle => _configuration.LogEventsHarvestCycle;
598601

src/Agent/NewRelic/Agent/Core/NewRelic.Agent.Core.Metric/MetricNames.cs

+11
Original file line numberDiff line numberDiff line change
@@ -1040,6 +1040,7 @@ public static string GetPerDestinationAreaDataUsageMetricName(string destination
10401040

10411041
private const string LoggingMetrics = "Logging";
10421042
private const string LoggingMetricsDotnetLines = LoggingMetrics + PathSeparator + "lines";
1043+
private const string LoggingMetricsDotnetDenied = LoggingMetrics + PathSeparator + "denied";
10431044
private const string SupportabilityLoggingEventsPs = SupportabilityPs + "Logging" + PathSeparator;
10441045
public const string SupportabilityLoggingEventsSent = SupportabilityLoggingEventsPs + Forwarding + PathSeparator + "Sent";
10451046
public const string SupportabilityLoggingEventsCollected = SupportabilityLoggingEventsPs + Forwarding + PathSeparator + "Seen";
@@ -1055,6 +1056,16 @@ public static string GetLoggingMetricsLinesName()
10551056
return LoggingMetricsDotnetLines;
10561057
}
10571058

1059+
public static string GetLoggingMetricsDeniedBySeverityName(string logLevel)
1060+
{
1061+
return LoggingMetricsDotnetDenied + PathSeparator + logLevel;
1062+
}
1063+
1064+
public static string GetLoggingMetricsDeniedName()
1065+
{
1066+
return LoggingMetricsDotnetDenied;
1067+
}
1068+
10581069
private const string Enabled = "enabled";
10591070
private const string Disabled = "disabled";
10601071
private const string Metrics = "Metrics";

src/Agent/NewRelic/Agent/Core/WireModels/IMetricBuilder.cs

+4
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,10 @@ public interface IMetricBuilder
196196

197197
MetricWireModel TryBuildLoggingMetricsLinesCountMetric(int count);
198198

199+
MetricWireModel TryBuildLoggingMetricsDeniedCountBySeverityMetric(string logLevel, int count);
200+
201+
MetricWireModel TryBuildLoggingMetricsDeniedCountMetric(int count);
202+
199203
MetricWireModel TryBuildSupportabilityLoggingEventsCollectedMetric();
200204

201205
MetricWireModel TryBuildSupportabilityLoggingEventsSentMetric(int loggingEventCount);

src/Agent/NewRelic/Agent/Core/WireModels/MetricWireModel.cs

+12
Original file line numberDiff line numberDiff line change
@@ -955,6 +955,18 @@ public MetricWireModel TryBuildLoggingMetricsLinesCountMetric(int count)
955955
return BuildMetric(_metricNameService, proposedName, null, MetricDataWireModel.BuildCountData(count));
956956
}
957957

958+
public MetricWireModel TryBuildLoggingMetricsDeniedCountBySeverityMetric(string logLevel, int count)
959+
{
960+
var proposedName = MetricNames.GetLoggingMetricsDeniedBySeverityName(logLevel);
961+
return BuildMetric(_metricNameService, proposedName, null, MetricDataWireModel.BuildCountData(count));
962+
}
963+
964+
public MetricWireModel TryBuildLoggingMetricsDeniedCountMetric(int count)
965+
{
966+
var proposedName = MetricNames.GetLoggingMetricsDeniedName();
967+
return BuildMetric(_metricNameService, proposedName, null, MetricDataWireModel.BuildCountData(count));
968+
}
969+
958970
public MetricWireModel TryBuildSupportabilityLoggingEventsCollectedMetric()
959971
{
960972
const string proposedName = MetricNames.SupportabilityLoggingEventsCollected;

src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Configuration/IConfiguration.cs

+1
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ public interface IConfiguration
190190
int LogEventsMaxSamplesStored { get; }
191191
TimeSpan LogEventsHarvestCycle { get; }
192192
bool LogDecoratorEnabled { get; }
193+
HashSet<string> LogLevelDenyList { get; }
193194
bool ContextDataEnabled { get; }
194195
IEnumerable<string> ContextDataInclude { get; }
195196
IEnumerable<string> ContextDataExclude { get; }

tests/Agent/IntegrationTests/IntegrationTestHelpers/NewRelicConfigModifier.cs

+7
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,13 @@ public NewRelicConfigModifier SetLogForwardingMaxSamplesStored(int samples)
317317
return this;
318318
}
319319

320+
public NewRelicConfigModifier SetLogForwardingLogLevelDenyList(string logLevelDenyList)
321+
{
322+
CommonUtils.ModifyOrCreateXmlNodeInNewRelicConfig(_configFilePath, new[] { "configuration", "applicationLogging" }, "forwarding", string.Empty);
323+
CommonUtils.ModifyOrCreateXmlAttributeInNewRelicConfig(_configFilePath, new[] { "configuration", "applicationLogging", "forwarding" }, "logLevelDenyList", logLevelDenyList);
324+
return this;
325+
}
326+
320327
public NewRelicConfigModifier SetCodeLevelMetricsEnabled(bool enabled = true)
321328
{
322329
CommonUtils.ModifyOrCreateXmlNodeInNewRelicConfig(_configFilePath, new[] { "configuration" }, "codeLevelMetrics", string.Empty);

0 commit comments

Comments
 (0)