Skip to content

feat: Internal logging now uses Serilog instead of log4net #1661

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 30, 2023
2 changes: 1 addition & 1 deletion src/Agent/NewRelic/Agent/Core/AgentInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ private static class CallOnce
{
static CallOnce()
{
// we must ensure that we hook up to ProcessExit and DomainUnload *before* log4net. Otherwise we can't log anything during OnExit.
// we must ensure that we hook up to ProcessExit and DomainUnload *before* log initialization. Otherwise we can't log anything during OnExit.
AppDomain.CurrentDomain.ProcessExit += (sender, args) => OnExit(sender, args);
AppDomain.CurrentDomain.DomainUnload += (sender, args) => OnExit(sender, args);
LoggerBootstrapper.Initialize();
Expand Down
38 changes: 0 additions & 38 deletions src/Agent/NewRelic/Agent/Core/Config/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1301,8 +1301,6 @@ public partial class configurationLog

private bool auditLogField;

private System.Nullable<configurationLogFileLockingModel> fileLockingModelField;

/// <summary>
/// configurationLog class constructor
/// </summary>
Expand Down Expand Up @@ -1381,42 +1379,6 @@ public bool auditLog
}
}

[System.Xml.Serialization.XmlAttributeAttribute()]
public configurationLogFileLockingModel fileLockingModel
{
get
{
if (this.fileLockingModelField.HasValue)
{
return this.fileLockingModelField.Value;
}
else
{
return default(configurationLogFileLockingModel);
}
}
set
{
this.fileLockingModelField = value;
}
}

[System.Xml.Serialization.XmlIgnoreAttribute()]
public bool fileLockingModelSpecified
{
get
{
return this.fileLockingModelField.HasValue;
}
set
{
if (value==false)
{
this.fileLockingModelField = null;
}
}
}

#region Clone method
/// <summary>
/// Create a clone of this configurationLog object
Expand Down
13 changes: 0 additions & 13 deletions src/Agent/NewRelic/Agent/Core/Config/Configuration.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -347,19 +347,6 @@
</xs:annotation>
</xs:attribute>

<xs:attribute name="fileLockingModel">
<xs:annotation>
<xs:documentation>
Controls how agent-produced log files are to be locked. Experimental.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="exclusive"/>
<xs:enumeration value="minimal"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>

Expand Down
16 changes: 0 additions & 16 deletions src/Agent/NewRelic/Agent/Core/Config/ConfigurationLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -586,22 +586,6 @@ private string GetLogFileName()
return "newrelic_agent_" + Strings.SafeFileName(name) + ".log";
}

public bool FileLockingModelSpecified
{
get
{
return fileLockingModelSpecified;
}
}

public configurationLogFileLockingModel FileLockingModel
{
get
{
return fileLockingModel;
}
}

public bool Console
{
get
Expand Down
3 changes: 0 additions & 3 deletions src/Agent/NewRelic/Agent/Core/Config/ILogConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ public interface ILogConfig

string GetFullLogFileName();

bool FileLockingModelSpecified { get; }
configurationLogFileLockingModel FileLockingModel { get; }

bool Console { get; }

bool IsAuditLogEnabled { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,8 @@ private void OnConfigurationDeserialized(ConfigurationDeserializedEvent configur

private static void UpdateLogLevel(configuration localConfiguration)
{
var hierarchy = log4net.LogManager.GetRepository(Assembly.GetCallingAssembly()) as log4net.Repository.Hierarchy.Hierarchy;
var logger = hierarchy.Root;

var logLevel = logger.Hierarchy.LevelMap[localConfiguration.LogConfig.LogLevel];
if (logLevel != null && logLevel != logger.Level)
{
Log.InfoFormat("The log level was updated to {0}", logLevel);
logger.Level = logLevel;
}
Log.InfoFormat("The log level was updated to {0}", localConfiguration.LogConfig.LogLevel);
LoggerBootstrapper.UpdateLoggingLevel(localConfiguration.LogConfig.LogLevel);
}

private void OnServerConfigurationUpdated(ServerConfigurationUpdatedEvent serverConfigurationUpdatedEvent)
Expand Down
29 changes: 19 additions & 10 deletions src/Agent/NewRelic/Agent/Core/Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,13 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Serilog" Version="2.12.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="ILRepack" Version="2.0.16" />
<PackageReference Include="log4net" Version="2.0.8" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="SharpZipLib" Version="1.3.3" />
</ItemGroup>
Expand All @@ -56,6 +60,7 @@
<PackageReference Include="Castle.Core" Version="3.3.0" />
<PackageReference Include="Castle.Windsor" Version="3.3.0" />
<PackageReference Include="System.Runtime.InteropServices.RuntimeInformation" Version="4.3.0" />
<PackageReference Include="Serilog.Sinks.EventLog" Version="3.1.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
Expand All @@ -79,11 +84,6 @@
<Reference Include="System.Configuration" />
</ItemGroup>

<ItemGroup>
<Compile Include="$(RootProjectDirectory)\src\Agent\Shared\SharedLog4NetRepository.cs">
<Link>Properties\SharedLog4NetRepository.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>
Expand All @@ -105,8 +105,13 @@
<ILRepackInclude Include="@(PossibleRefsForILRepack)" Condition="'%(FileName)' == 'Castle.Core'" />
<ILRepackInclude Include="@(PossibleRefsForILRepack)" Condition="'%(FileName)' == 'Castle.Windsor'" />
<ILRepackInclude Include="@(PossibleRefsForILRepack)" Condition="'%(FileName)' == 'ICSharpCode.SharpZipLib'" />
<ILRepackInclude Include="@(PossibleRefsForILRepack)" Condition="'%(FileName)' == 'log4net'" />
<ILRepackInclude Include="@(PossibleRefsForILRepack)" Condition="'%(FileName)' == 'Newtonsoft.Json'" />
<ILRepackInclude Include="@(PossibleRefsForILRepack)" Condition="'%(FileName)' == 'Serilog'" />
<ILRepackInclude Include="@(PossibleRefsForILRepack)" Condition="'%(FileName)' == 'Serilog.Sinks.Async'" />
<ILRepackInclude Include="@(PossibleRefsForILRepack)" Condition="'%(FileName)' == 'Serilog.Sinks.Console'" />
<ILRepackInclude Include="@(PossibleRefsForILRepack)" Condition="'%(FileName)' == 'Serilog.Sinks.Debug'" />
<ILRepackInclude Include="@(PossibleRefsForILRepack)" Condition="'%(FileName)' == 'Serilog.Sinks.EventLog'" />
<ILRepackInclude Include="@(PossibleRefsForILRepack)" Condition="'%(FileName)' == 'Serilog.Sinks.File'" />
<ILRepackInclude Include="@(PossibleRefsForILRepack)" Condition="'%(FileName)' == 'System.ValueTuple'" />
<ILRepackInclude Include="@(PossibleRefsForILRepack)" Condition="'%(FileName)' == 'System.Memory'" />
<ILRepackInclude Include="@(PossibleRefsForILRepack)" Condition="'%(FileName)' == 'System.Runtime.CompilerServices.Unsafe'" />
Expand All @@ -120,14 +125,18 @@
<ILRepackInclude Include="@(PossibleRefsForILRepack)" Condition="'%(FileName)' == 'Grpc.Net.Common'" />
<ILRepackInclude Include="@(PossibleRefsForILRepack)" Condition="'%(FileName)' == 'Grpc.Net.Client'" />
<ILRepackInclude Include="@(PossibleRefsForILRepack)" Condition="'%(FileName)' == 'Autofac'" />
<ILRepackInclude Include="@(PossibleRefsForILRepack)" Condition="'%(FileName)' == 'log4net'" />
<ILRepackInclude Include="@(PossibleRefsForILRepack)" Condition="'%(FileName)' == 'Newtonsoft.Json'" />
<ILRepackInclude Include="@(PossibleRefsForILRepack)" Condition="'%(FileName)' == 'Serilog'" />
<ILRepackInclude Include="@(PossibleRefsForILRepack)" Condition="'%(FileName)' == 'Serilog.Sinks.Async'" />
<ILRepackInclude Include="@(PossibleRefsForILRepack)" Condition="'%(FileName)' == 'Serilog.Sinks.Console'" />
<ILRepackInclude Include="@(PossibleRefsForILRepack)" Condition="'%(FileName)' == 'Serilog.Sinks.Debug'" />
<ILRepackInclude Include="@(PossibleRefsForILRepack)" Condition="'%(FileName)' == 'Serilog.Sinks.File'" />
<ILRepackInclude Include="@(PossibleRefsForILRepack)" Condition="'%(FileName)' == 'ICSharpCode.SharpZipLib'" />
</ItemGroup>

<PropertyGroup>
<ILRepackIncludeCount Condition="'$(TargetFramework)' == 'net462'">15</ILRepackIncludeCount>
<ILRepackIncludeCount Condition="'$(TargetFramework)' == 'netstandard2.0'">11</ILRepackIncludeCount>
<ILRepackIncludeCount Condition="'$(TargetFramework)' == 'net462'">20</ILRepackIncludeCount>
<ILRepackIncludeCount Condition="'$(TargetFramework)' == 'netstandard2.0'">15</ILRepackIncludeCount>
</PropertyGroup>

<Error Text="ILRepack of $(AssemblyName) ($(TargetFramework)) failed. A dependency is missing. Expected $(ILRepackIncludeCount) dependencies but found @(ILRepackInclude-&gt;Count())." Condition="@(ILRepackInclude-&gt;Count()) != $(ILRepackIncludeCount)" />
Expand Down
12 changes: 6 additions & 6 deletions src/Agent/NewRelic/Agent/Core/Labels/LabelsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace NewRelic.Agent.Core.Labels
{
public class LabelsService : ILabelsService
{
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(LabelsService));
private readonly Serilog.ILogger Log = Serilog.Log.Logger;

private const int MaxLabels = 64;
private const int MaxLength = 255;
Expand Down Expand Up @@ -45,18 +45,18 @@ private IEnumerable<Label> GetLabelsFromConfiguration()
.ToList();

if (labels.Count == MaxLabels)
Log.WarnFormat("Maximum number of labels reached, some may have been dropped.");
Log.Warning("Maximum number of labels reached, some may have been dropped.");

return labels;
}
catch (Exception exception)
{
Log.WarnFormat("Failed to parse labels configuration string: {0}", exception);
Log.Warning("Failed to parse labels configuration string: {0}", exception);
return Enumerable.Empty<Label>();
}
}

private static Label CreateLabelFromString(string typeAndValueString)
private Label CreateLabelFromString(string typeAndValueString)
{
if (typeAndValueString == null)
throw new ArgumentNullException("typeAndValueString");
Expand Down Expand Up @@ -87,11 +87,11 @@ private static Label CreateLabelFromString(string typeAndValueString)
return new Label(typeTruncated, valueTruncated);
}

private static string Truncate(string value)
private string Truncate(string value)
{
var result = value.TruncateUnicodeStringByLength(MaxLength);
if (result.Length != value.Length)
Log.WarnFormat("Truncated label key from {0} to {1}", value, result);
Log.Warning("Truncated label key from {0} to {1}", value, result);

return result;
}
Expand Down
8 changes: 4 additions & 4 deletions src/Agent/NewRelic/Agent/Core/Logging/AuditLog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ namespace NewRelic.Agent.Core.Logging
{
public static class AuditLog
{
private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(AuditLog));

/// <summary>
/// Logs <paramref name="message"/> at the AUDIT level, a custom log level that is not well-defined in popular logging providers like log4net. This log level should be used only as dictated by the security team to satisfy auditing requirements.
/// </summary>
public static void Log(string message)
{
var auditLogLevel = LoggerBootstrapper.GetAuditLevel();
Logger.Logger.Log(typeof(AuditLog), auditLogLevel, message, null);
// create a logger that injects an Audit property that we can later filter on (see LoggerBootstrapper.IncludeOnlyAuditLog)
var auditLogger = Serilog.Log.ForContext(LogLevelExtensions.AuditLevel, LogLevelExtensions.AuditLevel);
// use Fatal log level to ensure audit log messages never get filtered due to level restrictions
auditLogger.Fatal(message);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright 2020 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

using System.IO;
using Serilog.Events;
using Serilog.Formatting;

namespace NewRelic.Agent.Core
{
class CustomAuditLogTextFormatter : ITextFormatter
{
public void Format(LogEvent logEvent, TextWriter output)
{
logEvent.Properties.TryGetValue("Message", out var message);

output.Write($"{logEvent.Timestamp.ToUniversalTime():yyyy-MM-dd HH:mm:ss,fff} NewRelic AUDIT: {message}{output.NewLine}");
}
}
}
24 changes: 24 additions & 0 deletions src/Agent/NewRelic/Agent/Core/Logging/CustomTextFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright 2020 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

using System.IO;
using Serilog.Events;
using Serilog.Formatting;

namespace NewRelic.Agent.Core
{
class CustomTextFormatter : ITextFormatter
{
public void Format(LogEvent logEvent, TextWriter output)
{
// try to get process and thread id
logEvent.Properties.TryGetValue("pid", out var pid);
logEvent.Properties.TryGetValue("tid", out var tid);

// format matches the log4net format, but adds the Exception property (if present in the message as a separate property) to the end, after the newline
// if that's not desired, take the Exception property out and refactor calls to `Serilog.Log.Logger.LogXxxx(exception, message)` so the exception is
// formatted into the message
output.Write($"{logEvent.Timestamp.ToUniversalTime():yyyy-MM-dd HH:mm:ss,fff} NewRelic {logEvent.Level.TranslateLogLevel(),6}: [pid: {pid?.ToString()}, tid: {tid?.ToString()}] {logEvent.MessageTemplate}{output.NewLine}{logEvent.Exception}");
}
}
}
45 changes: 45 additions & 0 deletions src/Agent/NewRelic/Agent/Core/Logging/InMemorySink.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright 2020 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

using System;
using Serilog.Core;
using Serilog.Events;
using System.Collections.Concurrent;
using System.Collections.Generic;

namespace NewRelic.Agent.Core.Logging
{
public class InMemorySink : ILogEventSink, IDisposable
{
private readonly ConcurrentQueue<LogEvent> _logEvents;

public InMemorySink()
{
_logEvents = new ConcurrentQueue<LogEvent>();
}

public void Emit(LogEvent logEvent)
{
_logEvents.Enqueue(logEvent);
}

public IEnumerable<LogEvent> LogEvents
{
get
{
return _logEvents;
}
}

public void Clear()
{
while (_logEvents.TryDequeue(out _))
{ }
}

public void Dispose()
{
Clear();
}
}
}
Loading