これは私の質問のフォローアップです: Flexible Logging Interface ...
WinForms 2.0アプリケーション用に、複数行のTextBox用のカスタムlog4netアペンダーを作成したいと思います。 StackOverflowメンバーの1人devdigitalは、すでにこのリンクを示しています。
ただし、この記事では、Xmlファイルを介してこのようなアペンダーを構成する方法については説明していません。このアペンダーを構成する際の固有の問題は、TextBoxオブジェクトへの参照をこのアペンダーに渡す必要があることです。
それで、Xmlファイルを使用してそれを構成することはまったく可能ですか?または、そのようなアペンダーはプログラムでのみ構成できますか? Xmlファイルとコードの組み合わせを使用して、可能な限り構成可能または疎結合にするためのオプションは何ですか?
ありがとう。
Log4netの構成方法によって異なりますが、通常、log4netが構成を読み取るときにフォーム(したがってtextBoxes)は作成されません。したがって、フォーム名とテキストボックス名のプロパティを作成する必要があります。また、ログイベントを追加する直前に、フォームが開かれ、テキストボックスが提供されているかどうかを確認する必要があります。また、AppenderSkeleton
を最初から実装するよりも、IAppender
から継承することをお勧めします。
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);
}
}
構成は簡単です(log4netはxml要素を読み取り、同じ名前のプロパティの値を提供します):
<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>
質問はアペンダーの構成に関するものであるため、エラー処理コードやマルチスレッドおよびスレッド同期に関連するコードは提供しませんでした。
これはすべての上位コメントの更新バージョンです:スレッドセーフ、アプリケーションをロックせず、変換パターンを使用します:
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));
});
}
}
}
構成、これを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>
マルチスレッドで動作するようにアペンダーを変更しました。また、コード構成を添付しました。
よろしく、ドリン
アペンダー:
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);
});
}
}
構成:
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);
テキストボックスに追加される実際の行は...
_textBox.AppendText(RenderLoggingEvent(loggingEvent));
...パターンレイアウトを利用したい場合。それ以外の場合は、メッセージのテキスト(デフォルトのレイアウト)を送信するだけです。
Klodomaによる上記のサンプルはかなり良いです。テキストボックスをリッチテキストボックスに変更すると、出力をさらに活用できます。メッセージをレベルごとに色分けするためのコードを次に示します。
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));
});
本当に必要な場合は、ColorConsoleAppenderと同じ方法でlog4net構成から色をマップできますが、次のコーダーがこのサンプルに遭遇するために残しておきます...
アプリケーションの複数の場所でロギングを実行する場合は、以下のアプローチをお勧めします。このアプローチにより、コードを介して制御インスタンスを動的に変更できる柔軟性が得られます。
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);
}
}
テキストボックスインスタンスを割り当てるコード。ロギングを行うプロセスを開始する前に、これを実行してください。
var appender = LogManager.GetRepository().GetAppenders().Where(a => a.Name == "TextBoxAppender").FirstOrDefault();
if (appender != null)
((TextBoxAppender)appender).RichTextBox = this.richTextBoxLog;
構成
<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>
これがklodomaの回答のWPF/XAMLバージョンです
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));
}));
}
およびログ構成
<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>
ウィンドウに名前を付けることを忘れないでください(ウィンドウタイプ名とは異なる必要があります)