Skip to content

Commit

Permalink
summary: Log Level Filtering (#1770)
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
tippmar-nr committed Jul 13, 2023
1 parent 6c98ba2 commit aadce3a
Show file tree
Hide file tree
Showing 20 changed files with 534 additions and 7 deletions.
13 changes: 11 additions & 2 deletions src/Agent/NewRelic/Agent/Core/Agent.cs
Expand Up @@ -408,7 +408,7 @@ public void RecordSupportabilityMetric(string metricName, int count)
_agentHealthReporter.ReportSupportabilityCountMetric(metricName, count);
}

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)
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)
{
_agentHealthReporter.ReportLogForwardingFramework(frameworkName);

Expand All @@ -418,6 +418,15 @@ public void RecordLogMessage(string frameworkName, object logEvent, Func<object,
{
var level = getLevel(logEvent).ToString();
normalizedLevel = string.IsNullOrWhiteSpace(level) ? "UNKNOWN" : level.ToUpper();

// LogLevelDenyList is already uppercase, so don't need case-insensitive lookup
if (_configurationService.Configuration.LogLevelDenyList.Contains(normalizedLevel))
{
if (_configurationService.Configuration.LogMetricsCollectorEnabled)
_agentHealthReporter.IncrementLogDeniedCount(normalizedLevel);

return;
}
}

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

var logMessage = getLogMessage(logEvent);
var logException = getLogException(logEvent);

// exit quickly if the message and exception are missing
if (string.IsNullOrWhiteSpace(logMessage) && logException is null)
{
Expand Down
23 changes: 23 additions & 0 deletions src/Agent/NewRelic/Agent/Core/AgentHealth/AgentHealthReporter.cs
Expand Up @@ -29,6 +29,7 @@ public class AgentHealthReporter : ConfigurationBasedService, IAgentHealthReport
private readonly IList<RecurringLogData> _recurringLogDatas = new ConcurrentList<RecurringLogData>();
private readonly IDictionary<AgentHealthEvent, InterlockedCounter> _agentHealthEventCounters = new Dictionary<AgentHealthEvent, InterlockedCounter>();
private readonly ConcurrentDictionary<string, InterlockedCounter> _logLinesCountByLevel = new ConcurrentDictionary<string, InterlockedCounter>();
private readonly ConcurrentDictionary<string, InterlockedCounter> _logDeniedCountByLevel = new ConcurrentDictionary<string, InterlockedCounter>();

private PublishMetricDelegate _publishMetricDelegate;
private InterlockedCounter _payloadCreateSuccessCounter;
Expand Down Expand Up @@ -583,6 +584,22 @@ public void CollectLoggingMetrics()
_loggingForwardingEnabledWithFrameworksReported[kvp.Key] = true;
}
}

var totalDeniedCount = 0;
foreach (var logLinesDeniedCounter in _logDeniedCountByLevel)
{
if (TryGetCount(logLinesDeniedCounter.Value, out var linesCount))
{
totalDeniedCount += linesCount;
TrySend(_metricBuilder.TryBuildLoggingMetricsDeniedCountBySeverityMetric(logLinesDeniedCounter.Key, linesCount));
}
}

if (totalDeniedCount > 0)
{
TrySend(_metricBuilder.TryBuildLoggingMetricsDeniedCountMetric(totalDeniedCount));
}

}

public void IncrementLogLinesCount(string level)
Expand All @@ -591,6 +608,12 @@ public void IncrementLogLinesCount(string level)
_logLinesCountByLevel[level].Increment();
}

public void IncrementLogDeniedCount(string level)
{
_logDeniedCountByLevel.TryAdd(level, new InterlockedCounter());
_logDeniedCountByLevel[level].Increment();
}

public void ReportLoggingEventCollected() => TrySend(_metricBuilder.TryBuildSupportabilityLoggingEventsCollectedMetric());

public void ReportLoggingEventsSent(int count) => TrySend(_metricBuilder.TryBuildSupportabilityLoggingEventsSentMetric(count));
Expand Down
Expand Up @@ -142,6 +142,7 @@ public interface IAgentHealthReporter : IOutOfBandMetricSource
void ReportSupportabilityDataUsage(string api, string apiArea, long dataSent, long dataReceived);

void IncrementLogLinesCount(string logLevel);
void IncrementLogDeniedCount(string logLevel);
void ReportLoggingEventCollected();
void ReportLoggingEventsSent(int count);
void ReportLoggingEventsDropped(int droppedCount);
Expand Down
15 changes: 15 additions & 0 deletions src/Agent/NewRelic/Agent/Core/Config/Configuration.cs
Expand Up @@ -5035,6 +5035,8 @@ public partial class configurationApplicationLoggingForwarding

private int maxSamplesStoredField;

private string logLevelDenyListField;

/// <summary>
/// configurationApplicationLoggingForwarding class constructor
/// </summary>
Expand Down Expand Up @@ -5085,6 +5087,19 @@ public int maxSamplesStored
}
}

[System.Xml.Serialization.XmlAttributeAttribute()]
public string logLevelDenyList
{
get
{
return this.logLevelDenyListField;
}
set
{
this.logLevelDenyListField = value;
}
}

#region Clone method
/// <summary>
/// Create a clone of this configurationApplicationLoggingForwarding object
Expand Down
9 changes: 9 additions & 0 deletions src/Agent/NewRelic/Agent/Core/Config/Configuration.xsd
Expand Up @@ -1655,6 +1655,15 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>

<xs:attribute name="logLevelDenyList" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>
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.
</xs:documentation>
</xs:annotation>
</xs:attribute>

</xs:complexType>
</xs:element>
<xs:element name="localDecorating" minOccurs="0" maxOccurs="1">
Expand Down
Expand Up @@ -1990,6 +1990,31 @@ public virtual bool LogDecoratorEnabled
}
}

private HashSet<string> _logLevelDenyList;
public virtual HashSet<string> LogLevelDenyList
{
get
{
if (_logLevelDenyList == null)
{
_logLevelDenyList = new HashSet<string>(
EnvironmentOverrides(_localConfiguration.applicationLogging.forwarding.logLevelDenyList,
"NEW_RELIC_APPLICATION_LOGGING_FORWARDING_LOG_LEVEL_DENYLIST")
?.Split(new[] { StringSeparators.CommaChar, ' ' }, StringSplitOptions.RemoveEmptyEntries)
.Select(s => s.ToUpper())
?? Enumerable.Empty<string>());

if (_logLevelDenyList.Count > 0)
{
var logLevels = string.Join(",", _logLevelDenyList);
Log.Info($"Log Level Filtering is enabled for the following levels: {logLevels}");
}
}

return _logLevelDenyList;
}
}

#endregion

public virtual bool AppDomainCachingDisabled
Expand Down
Expand Up @@ -593,6 +593,9 @@ public ReportedConfiguration(IConfiguration configuration)
[JsonProperty("application_logging.forwarding.max_samples_stored")]
public int LogEventsMaxSamplesStored => _configuration.LogEventsMaxSamplesStored;

[JsonProperty("application_logging.forwarding.log_level_denylist")]
public HashSet<string> LogLevelDenyList => _configuration.LogLevelDenyList;

[JsonProperty("application_logging.harvest_cycle")]
public TimeSpan LogEventsHarvestCycle => _configuration.LogEventsHarvestCycle;

Expand Down
Expand Up @@ -1040,6 +1040,7 @@ public static string GetPerDestinationAreaDataUsageMetricName(string destination

private const string LoggingMetrics = "Logging";
private const string LoggingMetricsDotnetLines = LoggingMetrics + PathSeparator + "lines";
private const string LoggingMetricsDotnetDenied = LoggingMetrics + PathSeparator + "denied";
private const string SupportabilityLoggingEventsPs = SupportabilityPs + "Logging" + PathSeparator;
public const string SupportabilityLoggingEventsSent = SupportabilityLoggingEventsPs + Forwarding + PathSeparator + "Sent";
public const string SupportabilityLoggingEventsCollected = SupportabilityLoggingEventsPs + Forwarding + PathSeparator + "Seen";
Expand All @@ -1055,6 +1056,16 @@ public static string GetLoggingMetricsLinesName()
return LoggingMetricsDotnetLines;
}

public static string GetLoggingMetricsDeniedBySeverityName(string logLevel)
{
return LoggingMetricsDotnetDenied + PathSeparator + logLevel;
}

public static string GetLoggingMetricsDeniedName()
{
return LoggingMetricsDotnetDenied;
}

private const string Enabled = "enabled";
private const string Disabled = "disabled";
private const string Metrics = "Metrics";
Expand Down
4 changes: 4 additions & 0 deletions src/Agent/NewRelic/Agent/Core/WireModels/IMetricBuilder.cs
Expand Up @@ -196,6 +196,10 @@ public interface IMetricBuilder

MetricWireModel TryBuildLoggingMetricsLinesCountMetric(int count);

MetricWireModel TryBuildLoggingMetricsDeniedCountBySeverityMetric(string logLevel, int count);

MetricWireModel TryBuildLoggingMetricsDeniedCountMetric(int count);

MetricWireModel TryBuildSupportabilityLoggingEventsCollectedMetric();

MetricWireModel TryBuildSupportabilityLoggingEventsSentMetric(int loggingEventCount);
Expand Down
12 changes: 12 additions & 0 deletions src/Agent/NewRelic/Agent/Core/WireModels/MetricWireModel.cs
Expand Up @@ -955,6 +955,18 @@ public MetricWireModel TryBuildLoggingMetricsLinesCountMetric(int count)
return BuildMetric(_metricNameService, proposedName, null, MetricDataWireModel.BuildCountData(count));
}

public MetricWireModel TryBuildLoggingMetricsDeniedCountBySeverityMetric(string logLevel, int count)
{
var proposedName = MetricNames.GetLoggingMetricsDeniedBySeverityName(logLevel);
return BuildMetric(_metricNameService, proposedName, null, MetricDataWireModel.BuildCountData(count));
}

public MetricWireModel TryBuildLoggingMetricsDeniedCountMetric(int count)
{
var proposedName = MetricNames.GetLoggingMetricsDeniedName();
return BuildMetric(_metricNameService, proposedName, null, MetricDataWireModel.BuildCountData(count));
}

public MetricWireModel TryBuildSupportabilityLoggingEventsCollectedMetric()
{
const string proposedName = MetricNames.SupportabilityLoggingEventsCollected;
Expand Down
Expand Up @@ -190,6 +190,7 @@ public interface IConfiguration
int LogEventsMaxSamplesStored { get; }
TimeSpan LogEventsHarvestCycle { get; }
bool LogDecoratorEnabled { get; }
HashSet<string> LogLevelDenyList { get; }
bool ContextDataEnabled { get; }
IEnumerable<string> ContextDataInclude { get; }
IEnumerable<string> ContextDataExclude { get; }
Expand Down
Expand Up @@ -317,6 +317,13 @@ public NewRelicConfigModifier SetLogForwardingMaxSamplesStored(int samples)
return this;
}

public NewRelicConfigModifier SetLogForwardingLogLevelDenyList(string logLevelDenyList)
{
CommonUtils.ModifyOrCreateXmlNodeInNewRelicConfig(_configFilePath, new[] { "configuration", "applicationLogging" }, "forwarding", string.Empty);
CommonUtils.ModifyOrCreateXmlAttributeInNewRelicConfig(_configFilePath, new[] { "configuration", "applicationLogging", "forwarding" }, "logLevelDenyList", logLevelDenyList);
return this;
}

public NewRelicConfigModifier SetCodeLevelMetricsEnabled(bool enabled = true)
{
CommonUtils.ModifyOrCreateXmlNodeInNewRelicConfig(_configFilePath, new[] { "configuration" }, "codeLevelMetrics", string.Empty);
Expand Down

0 comments on commit aadce3a

Please sign in to comment.