Как войти в систему MethodName при переносе Log4net?
Я завернул Log4net в статическую оболочку и хочу войти
loggingEvent.LocationInformation.MethodName
loggingEvent.LocationInformation.ClassName
Однако все, что я получаю, это имя моей обертки.
Как я могу записать эту информацию, используя forwardingappender и статический класс-оболочку, например
Logger.Debug("Logging to Debug");
Logger.Info("Logging to Info");
Logger.Warn("Logging to Warn");
Logger.Error(ex);
Logger.Fatal(ex);
11 ответов
Что ж, ошибка была где-то в моем приложении, но для полноты изложения я включил ответ, насколько мне известно:
Фасад нужно обернуть ILogger, а НЕ ILog
public static class Logger
{
private readonly static Type ThisDeclaringType = typeof(Logger);
private static readonly ILogger defaultLogger;
static Logger()
{
defaultLogger =
LoggerManager.GetLogger(Assembly.GetCallingAssembly(),"MyDefaultLoggger");
...
public static void Info(string message)
{
if (defaultLogger.IsEnabledFor(infoLevel))
{
defaultLogger.Log(typeof(Logger), infoLevel, message, null);
}
}
Что насчет %M
а также %C
переменные? http://logging.apache.org/log4net/log4net-1.2.11/release/sdk/log4net.Layout.PatternLayout.html
Использование, что-то вроде:
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%M %C] - %message%newline" />
</layout>
Разве это не делает то, что вы после?
Я бы просто использовал что-то вроде %stacktrace{2}
в качестве шаблона преобразования.
Пример вывода:
MyNamespace.ClassName.Method> Common.Log.Warning
где MyNamespace.ClassName.Method
это метод, который вызывает мою обертку и Common.Log.Warning
является методом класса-оболочки
Шаблоны конверсии можно найти здесь.
Просто объявите свою переменную журнала следующим образом...
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
Тогда вы можете использовать его как обычно.
Я реализовал для этого следующее решение (.Net framework 4.5+): методы оболочки log4net (например, Info, Warn, Error) могут использовать CallerMemberName и CallerFilePath для получения имени класса и метода кода, из которого ведутся журналы. называется. Затем вы можете объединить их в настраиваемое свойство log4net.
Не стесняйтесь использовать свою собственную реализацию оболочки log4net, единственные важные вещи здесь: подпись методов Info и Error и реализация метода GetLogger.
Аргументы memberName и sourceFilePath никогда не должны указываться при вызове методов Logger.Info или Logger.Error, они автоматически заполняются.Net.
public static class Logger
{
private class LogSingletonWrapper
{
public ILog Log { get; set; }
public LogSingletonWrapper()
{
Log = LogManager.GetLogger(GetType());
}
}
private static readonly Lazy<LogSingletonWrapper> _logger = new Lazy<LogSingletonWrapper>();
public static void Info(string message, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "")
=> GetLogger(memberName, sourceFilePath).Info(message);
public static void Error(string message,Exception ex, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "")
=> GetLogger(memberName, sourceFilePath).Error(message, ex);
private static ILog GetLogger(string memberName, string sourceFilePath)
{
var classname = sourceFilePath.Split('\\').Last().Split('.').First();
log4net.ThreadContext.Properties["Source"] = $"{classname}.{memberName.Replace(".", "")}";
return _logger.Value.Log;
}
}
Тогда вы могли бы использовать шаблон преобразования журнала, подобный этому, в вашем файле.config:
<conversionPattern value="[%level][%date][Thd%thread: %property{Source}][Message: %message]%newline" />
Это приведет к тому, что журналы будут выглядеть так:
[ИНФОРМАЦИЯ][2019-07-03 16:42:00,936][Thd1: Application.Start][Сообщение: приложение запускается...]
[ОШИБКА][2019-07-03 16:42:44,145][Thd6: DataProcessor.ProcessDataBatch][Сообщение: Попытка разделить на ноль.]
В приведенном выше примере были вызваны следующие методы: метод Start класса Application и метод ProcessDataBatch класса DataProcessor.
Этот пост помог мне разобраться, как написать свою собственную оболочку, так что взамен я подумал, что вам может понравиться мой полный класс, чтобы обернуть регистратор, который, кажется, работает довольно хорошо и на самом деле занимает чуть больше половины времени, чем непосредственное использование ILog!
Все, что требуется, это соответствующий XML для настройки регистрации в файле конфигурации и
[assembly: log4net.Config.XmlConfigurator(Watch = true)]
в вашем AssemblyInfo.cs, и он должен работать легко.
Одно замечание: я использую Log4NetDash с очень простой настройкой, поэтому обманул и поместил некоторую информацию в неправильные поля (например, трассировка стека в поле Домена), это все еще работает для меня, так как мне все равно, где информация показано, но вы можете исправить это, если вы все настраиваете правильно, если у вас есть свободное время!
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Threading;
using log4net;
using log4net.Core;
namespace Utility
{
public class Logger
{
static Logger()
{
LogManager.GetLogger(typeof(Logger));
}
public static void Debug(string message, params object[] parameters)
{
Log(message, Level.Debug, null, parameters);
}
public static void Info(string message, params object[] parameters)
{
Log(message, Level.Info, null, parameters);
}
public static void Warn(string message, params object[] parameters)
{
Log(message, Level.Warn, null, parameters);
}
public static void Error(string message, params object[] parameters)
{
Error(message, null, parameters);
}
public static void Error(Exception exception)
{
if (exception==null)
return;
Error(exception.Message, exception);
}
public static void Error(string message, Exception exception, params object[] parameters)
{
string exceptionStack = "";
if (exception != null)
{
exceptionStack = exception.GetType().Name + " : " + exception.Message + Environment.NewLine;
Exception loopException = exception;
while (loopException.InnerException != null)
{
loopException = loopException.InnerException;
exceptionStack += loopException.GetType().Name + " : " + loopException.Message + Environment.NewLine;
}
}
Log(message, Level.Error, exceptionStack, parameters);
}
private static void Log(string message, Level logLevel, string exceptionMessage, params object[] parameters)
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += LogEvent;
worker.RunWorkerAsync(new LogMessageSpec
{
ExceptionMessage = exceptionMessage,
LogLevel = logLevel,
Message = message,
Parameters = parameters,
Stack = new StackTrace(),
LogTime = DateTime.Now
});
}
private static void LogEvent(object sender, DoWorkEventArgs e)
{
try
{
LogMessageSpec messageSpec = (LogMessageSpec) e.Argument;
StackFrame frame = messageSpec.Stack.GetFrame(2);
MethodBase method = frame.GetMethod();
Type reflectedType = method.ReflectedType;
ILogger log = LoggerManager.GetLogger(reflectedType.Assembly, reflectedType);
Level currenLoggingLevel = ((log4net.Repository.Hierarchy.Logger) log).Parent.Level;
if (messageSpec.LogLevel<currenLoggingLevel)
return;
messageSpec.Message = string.Format(messageSpec.Message, messageSpec.Parameters);
string stackTrace = "";
StackFrame[] frames = messageSpec.Stack.GetFrames();
if (frames != null)
{
foreach (StackFrame tempFrame in frames)
{
MethodBase tempMethod = tempFrame.GetMethod();
stackTrace += tempMethod.Name + Environment.NewLine;
}
}
string userName = Thread.CurrentPrincipal.Identity.Name;
LoggingEventData evdat = new LoggingEventData
{
Domain = stackTrace,
Identity = userName,
Level = messageSpec.LogLevel,
LocationInfo = new LocationInfo(reflectedType.FullName,
method.Name,
frame.GetFileName(),
frame.GetFileLineNumber().ToString()),
LoggerName = reflectedType.Name,
Message = messageSpec.Message,
TimeStamp = messageSpec.LogTime,
UserName = userName,
ExceptionString = messageSpec.ExceptionMessage
};
log.Log(new LoggingEvent(evdat));
}
catch (Exception)
{}//don't throw exceptions on background thread especially about logging!
}
private class LogMessageSpec
{
public StackTrace Stack { get; set; }
public string Message { get; set; }
public Level LogLevel { get; set; }
public string ExceptionMessage { get; set; }
public object[] Parameters { get; set; }
public DateTime LogTime { get; set; }
}
}
}
Как насчет информации о звонках в C#4.5 - http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.callermembernameattribute.aspx
Я просто напишу больше кода правильного ответа Клауса
В классе обертки
public static class Logger
{
private static readonly ILogger DefaultLogger;
static Logger()
{
defaultLogger = LoggerManager.GetLogger(Assembly.GetCallingAssembly(), "MyDefaultLoggger"); // MyDefaultLoggger is the name of Logger
}
public static void LogError(object message)
{
Level errorLevel = Level.Error;
if (DefaultLogger.IsEnabledFor(errorLevel))
{
DefaultLogger.Log(typeof(Logger), errorLevel, message, null);
}
}
public static void LogError(object message, Exception exception)
{
Level errorLevel = Level.Error;
if (DefaultLogger.IsEnabledFor(errorLevel))
{
DefaultLogger.Log(typeof(Logger), errorLevel, message, exception);
}
}
и так далее для остальных методов.
в web.config или app.config log4net.Layout.PatternLayout вы можете использовать некоторые шаблоны преобразования, такие как:
%location %method %line
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date{dd/MM/yyyy hh:mm:ss.fff tt} [%thread] %level %logger [%location %method %line] [%C %M] - %newline%message%newline%exception"/>
</layout>
Щелкните здесь, чтобы узнать, как реализовать log4net в.NET Core 2.2
Следующие шаги взяты из указанной выше ссылки и объясняют, как добавить log4net в проект.NET Core 2.2.
Сначала выполните следующую команду в консоли Package-Manager:
Install-Package Log4Net_Logging -Version 1.0.0
Затем добавьте log4net.config со следующей информацией (пожалуйста, отредактируйте ее в соответствии с вашими настройками):
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<log4net>
<appender name="FileAppender" type="log4net.Appender.FileAppender">
<file value="logfile.log" />
<appendToFile value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%d [%t] %-5p - %m%n" />
</layout>
</appender>
<root>
<!--LogLevel: OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL -->
<level value="ALL" />
<appender-ref ref="FileAppender" />
</root>
</log4net>
</configuration>
Затем добавьте следующий код в контроллер (это пример, отредактируйте его перед добавлением в контроллер):
public ValuesController()
{
LogFourNet.SetUp(Assembly.GetEntryAssembly(), "log4net.config");
}
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
LogFourNet.Info(this, "This is Info logging");
LogFourNet.Debug(this, "This is Debug logging");
LogFourNet.Error(this, "This is Error logging");
return new string[] { "value1", "value2" };
}
Затем вызовите соответствующее действие контроллера (используя приведенный выше пример, вызовите /Values/Get
с HTTP GET), и вы получите результат, соответствующий следующему:
2019-06-05 19:58:45,103 [9] INFO-[Log4NetLogging_Project.Controllers.ValuesController.Get:23] - это ведение журнала информации
В аналогичной ситуации вместо того, чтобы полагаться на трассировку стека (как это делает log4net), я хотел получить имя метода из[CallerMemberName]
атрибут. Немного повозившись, я придумал метод обертки, подобный следующему:
// Works with a pattern like: (%file.%method:%line) %message%newline
public void WriteLog(ILogger logger, Level level, object msg, Exception? ex = null, [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNo = 0)
{
if (!logger.IsEnabledFor(level)) { return; }
RendererMap rendererMap = logger.Repository.RendererMap;
string fileName = Path.GetFileNameWithoutExtension(filePath);
LoggingEventData data = new LoggingEventData()
{
Level = level,
Message = rendererMap.FindAndRender(msg),
ExceptionString = ex is null ? "" : rendererMap.FindAndRender(ex),
LoggerName = logger.Name,
LocationInfo = new LocationInfo(fileName, memberName, fileName, lineNo.ToString(CultureInfo.InvariantCulture)),
TimeStampUtc = DateTime.UtcNow,
};
const FixFlags flags = FixFlags.All & ~FixFlags.ThreadName;
LoggingEvent e = new LoggingEvent(null, logger.Repository, data, flags);
logger.Log(e);
}
В этом примере переопределяется информация о местоположении log4net. Возможно, вам покажется более простым ответ @Quantum_Joe , который просто помещает информацию о местоположении вThreadContext.Properties
в отдельности.
Единственное, о чем я могу подумать (поскольку в настоящее время я не использую log4net), это запросить трассировку стека (новый StackTrace) и вернуться назад к фрейму, чтобы получить необходимую информацию. Однако я не уверен в том, как это повлияет на производительность во время выполнения.