c# - studio - log4net levels
Configuración de log4net TextBoxAppender(appender personalizado) a través de un archivo Xml (7)
Esto está en el seguimiento de mi pregunta: Interfaz de registro flexible ...
Ahora quiero escribir un appender log4net personalizado para un TextBox multilínea, para mi aplicación WinForms 2.0. Uno de los miembros de StackOverflow devdigital ya me ha señalado este enlace:
Sin embargo, el artículo no describe cómo configurar tal appender a través de un archivo Xml. El problema único al configurar este appender es que necesitamos pasar una referencia a un objeto TextBox a este appender.
Entonces, ¿es posible configurarlo usando un archivo Xml? ¿O se pueden configurar de manera programática tales agregadores? ¿Cuáles son las opciones para que sea lo más configurable o lo más débil posible posible, utilizando una combinación de código y archivo Xml?
Gracias.
Aquí hay una versión WPF / XAML de la respuesta de klodoma
public class TextBoxAppender : AppenderSkeleton {
private TextBox AppenderTextBox { get; set; }
private Window window;
public string WindowName { get; set; }
public string TextBoxName { get; set; }
private T FindControl<T>(Control root, string textBoxName) where T:class{
if (root.Name == textBoxName) {
return root as T;
}
return root.FindName(textBoxName) as T;
}
protected override void Append(log4net.Core.LoggingEvent loggingEvent) {
if (window == null || AppenderTextBox == null) {
if (string.IsNullOrEmpty(WindowName) ||
string.IsNullOrEmpty(TextBoxName))
return;
foreach (Window window in Application.Current.Windows) {
if (window.Name == WindowName) {
this.window = window;
}
}
if (window == null)
return;
AppenderTextBox = FindControl<TextBox>(window, TextBoxName);
if (AppenderTextBox == null)
return;
window.Closing += (s, e) => AppenderTextBox = null;
}
window.Dispatcher.BeginInvoke( new Action(delegate {
AppenderTextBox.AppendText(RenderLoggingEvent(loggingEvent));
}));
}
y la configuración de registro
<appender name="textboxAppender" type="Namespace.TextBoxAppender, Namespace">
<windowName value="Viewer"/>
<textBoxName value="LogBox"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
No olvide dar un nombre a su ventana (debe ser diferente al nombre del tipo de ventana)
Aquí hay una versión actualizada de todos los comentarios superiores: seguro para subprocesos, no bloquea la aplicación y usa el patrón de conversión:
namespace MyNamespace
{
public class TextBoxAppender : AppenderSkeleton
{
private TextBox _textBox;
public TextBox AppenderTextBox
{
get
{
return _textBox;
}
set
{
_textBox = value;
}
}
public string FormName { get; set; }
public string TextBoxName { get; set; }
private Control FindControlRecursive(Control root, string textBoxName)
{
if (root.Name == textBoxName) return root;
foreach (Control c in root.Controls)
{
Control t = FindControlRecursive(c, textBoxName);
if (t != null) return t;
}
return null;
}
protected override void Append(log4net.Core.LoggingEvent loggingEvent)
{
if (_textBox == null)
{
if (String.IsNullOrEmpty(FormName) ||
String.IsNullOrEmpty(TextBoxName))
return;
Form form = Application.OpenForms[FormName];
if (form == null)
return;
_textBox = (TextBox)FindControlRecursive(form, TextBoxName);
if (_textBox == null)
return;
form.FormClosing += (s, e) => _textBox = null;
}
_textBox.BeginInvoke((MethodInvoker)delegate
{
_textBox.AppendText(RenderLoggingEvent(loggingEvent));
});
}
}
}
La configuración, coloque esto en app.config:
<appender name="textboxAppender" type="MyNamespace.TextBoxAppender, MyNamespace">
<formName value="MainForm"/>
<textBoxName value="textBoxLog"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<root>
<level value="DEBUG" />
<appender-ref ref="RollingFileAppender" />
<appender-ref ref="textboxAppender" />
</root>
La línea real que se agrega al cuadro de texto debe ser ...
_textBox.AppendText(RenderLoggingEvent(loggingEvent));
... si quieres aprovechar un diseño de patrón. De lo contrario, simplemente envía el texto del mensaje (el diseño predeterminado).
La muestra anterior de Klodoma es bastante buena. Si cambia el cuadro de texto a un cuadro de texto enriquecido, puede hacer más con la salida. Aquí hay un código para colorear los mensajes de código por nivel:
System.Drawing.Color text_color;
switch (loggingEvent.Level.DisplayName.ToUpper())
{
case "FATAL":
text_color = System.Drawing.Color.DarkRed;
break;
case "ERROR":
text_color = System.Drawing.Color.Red;
break;
case "WARN":
text_color = System.Drawing.Color.DarkOrange;
break;
case "INFO":
text_color = System.Drawing.Color.Teal;
break;
case "DEBUG":
text_color = System.Drawing.Color.Green;
break;
default:
text_color = System.Drawing.Color.Black;
break;
}
_TextBox.BeginInvoke((MethodInvoker)delegate
{
_TextBox.SelectionColor = text_color;
_TextBox.AppendText(RenderLoggingEvent(loggingEvent));
});
Si realmente lo desea, los colores podrían asignarse desde la configuración de log4net de la misma manera que el ColorConsoleAppender, pero lo dejo para que el siguiente codificador tropiece en esta muestra ...
Modifiqué el appender para trabajar con multithreading. Además, adjunto la configuración del código.
Saludos, Dorin
Appender:
public class TextBoxAppender : AppenderSkeleton
{
private TextBox _textBox;
public TextBox AppenderTextBox
{
get
{
return _textBox;
}
set
{
_textBox = value;
}
}
public string FormName { get; set; }
public string TextBoxName { get; set; }
private Control FindControlRecursive(Control root, string textBoxName)
{
if (root.Name == textBoxName) return root;
foreach (Control c in root.Controls)
{
Control t = FindControlRecursive(c, textBoxName);
if (t != null) return t;
}
return null;
}
protected override void Append(log4net.Core.LoggingEvent loggingEvent)
{
if (_textBox == null)
{
if (String.IsNullOrEmpty(FormName) ||
String.IsNullOrEmpty(TextBoxName))
return;
Form form = Application.OpenForms[FormName];
if (form == null)
return;
_textBox = (TextBox)FindControlRecursive(form, TextBoxName);
if (_textBox == null)
return;
form.FormClosing += (s, e) => _textBox = null;
}
_textBox.Invoke((MethodInvoker)delegate
{
_textBox.AppendText(loggingEvent.RenderedMessage + Environment.NewLine);
});
}
}
Configuración:
var textBoxAppender = new Util.TextBoxAppender();
textBoxAppender.TextBoxName = "textLog";
textBoxAppender.FormName = "MainTarget";
textBoxAppender.Threshold = log4net.Core.Level.All;
var consoleAppender = new log4net.Appender.ConsoleAppender { Layout = new log4net.Layout.SimpleLayout() };
var list = new AppenderSkeleton[] { textBoxAppender, consoleAppender };
log4net.Config.BasicConfigurator.Configure(list);
Preferiría el siguiente enfoque si desea realizar el registro en varios lugares de su aplicación. Este enfoque proporciona la flexibilidad para cambiar la instancia de control dinámicamente a través del código.
TextBoxAppender
public class TextBoxAppender : AppenderSkeleton
{
public RichTextBox RichTextBox { get; set; }
protected override void Append(LoggingEvent loggingEvent)
{
Action operation = () => { this.RichTextBox.AppendText(RenderLoggingEvent(loggingEvent)); };
this.RichTextBox.Invoke(operation);
}
}
El código para asignar la instancia de cuadro de texto. Haga esto antes de iniciar el proceso que realiza el registro.
var appender = LogManager.GetRepository().GetAppenders().Where(a => a.Name == "TextBoxAppender").FirstOrDefault();
if (appender != null)
((TextBoxAppender)appender).RichTextBox = this.richTextBoxLog;
La configuración
<log4net debug="false">
<appender name="TextBoxAppender" type="SecurityAudit.UI.TextBoxAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<root>
<priority value="DEBUG" />
<appender-ref ref="TextBoxAppender" />
</root>
</log4net>
Depende de la forma en que configure log4net, pero generalmente no se crearán formularios (y, por lo tanto, cuadros de texto) cuando log4net lea la configuración. Por lo tanto, debe crear propiedades para los nombres de formulario y de cuadro de texto. Y debe verificar si el formulario está abierto y ha proporcionado un cuadro de texto justo antes de agregar el evento de registro. También es mejor heredar de AppenderSkeleton
que implementar IAppender
desde cero:
public class TextBoxAppender : AppenderSkeleton
{
private TextBox _textBox;
public string FormName { get; set; }
public string TextBoxName { get; set; }
protected override void Append(LoggingEvent loggingEvent)
{
if (_textBox == null)
{
if (String.IsNullOrEmpty(FormName) ||
String.IsNullOrEmpty(TextBoxName))
return;
Form form = Application.OpenForms[FormName];
if (form == null)
return;
_textBox = form.Controls[TextBoxName] as TextBox;
if (_textBox == null)
return;
form.FormClosing += (s, e) => _textBox = null;
}
_textBox.AppendText(loggingEvent.RenderedMessage + Environment.NewLine);
}
}
La configuración es simple (log4net leerá elementos xml y proporcionará valores para propiedades con los mismos nombres):
<appender name="textbox" type="Foo.TextBoxAppender, Foo">
<formName value="Form1"/>
<textBoxName value="textBox1"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %-5level %logger - %message" />
</layout>
</appender>
<root>
<level value="INFO" />
<appender-ref ref="textbox"/>
</root>
No proporcioné ningún código de manejo de errores o código relacionado con multihilo y sincronización de hilos, porque la pregunta es acerca de la configuración del appender.