普通に TraceListener を作ると、既に文字列に変換されたログを書き出すしか出来ませんが、Trace クラスには Error / Warning / Infomation を出し分けるメソッドが用意されているので、そういった情報を保持したままログを書き出したいと思いました。
TraceListener は Write と WriteLine が abstract になっていますが、それ以外のメソッドも virtual になっているのでオーバーライドが可能です。
なので、Event Hubs に JSON で構造化したログを吐き出す TraceListener を作成してみました。
public class EventHubTraceListener : TraceListener { public EventHubTraceListener() : this(ConfigurationManager.AppSettings["Microsoft.ServiceBus.ConnectionString"]) { } public EventHubTraceListener(string connectionString) { _client = EventHubClient.CreateFromConnectionString(connectionString); } private readonly EventHubClient _client; public override void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, object data) { TraceData(eventCache, source, eventType, id, new[] { data }); } public override void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, params object[] data) { if (Filter != null && !Filter.ShouldTrace(eventCache, source, eventType, id, null, null, null, data)) { return; } SendTraceEvent(eventCache, eventType, string.Join(",", data)); } public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id) { TraceEvent(eventCache, source, eventType, id, string.Empty); } public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message) { TraceEvent(eventCache, source, eventType, id, message, new object[0]); } public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args) { if (Filter != null && !Filter.ShouldTrace(eventCache, source, eventType, id, format, args, null, null)) { return; } SendTraceEvent(eventCache, eventType, string.Format(format, args)); } public override void Write(string message) { var obj = new { Message = message }; var json = JsonConvert.SerializeObject(obj); _client.Send(new EventData(Encoding.UTF8.GetBytes(json))); } public override void WriteLine(string message) { Write(message); } private void SendTraceEvent(TraceEventCache eventCache, TraceEventType eventType, string message) { var traceData = new TraceEventData { EventType = eventType.ToString(), DateTime = eventCache.DateTime, ProcessId = eventCache.ProcessId, ThreadId = eventCache.ThreadId, Timestamp = eventCache.Timestamp, Callstack = eventCache.Callstack, Message = message }; var json = JsonConvert.SerializeObject(traceData); _client.Send(new EventData(Encoding.UTF8.GetBytes(json))); } public class TraceEventData { public string EventType { get; set; } public DateTime DateTime { get; set; } public int ProcessId { get; set; } public string ThreadId { get; set; } public long Timestamp { get; set; } public string Callstack { get; set; } public string Message { get; set; } } }
Event Hubs の接続文字列は initializeData で渡してもいい気がしましたが、App Settings から取るようにしました。カスタム属性を使うことも考えましたが、初期化のタイミングがずれるので止めておきました。
使う場合は Web.config や App.config で TraceListener と Service Bus 接続文字列を追加します。
<?xml version="1.0" encoding="utf-8"?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/> </startup> <appSettings> <!-- Service Bus specific app setings for messaging connections --> <add key="Microsoft.ServiceBus.ConnectionString" value="Endpoint=sb://{namespace}.servicebus.windows.net/;SharedAccessKeyName=Send;SharedAccessKey={key};EntityPath={eventhub name}"/> </appSettings> <system.diagnostics> <trace> <listeners> <add name="eventhub" type="ConsoleApplication27.EventHubTraceListener, ConsoleApplication27" /> </listeners> </trace> </system.diagnostics> </configuration>
設定が終われば、あとは Trace.TraceError などのメソッドを呼び出せば、Event Hubs にログの JSON が流れていきます。簡単に確認するために、Azure Functions の Event Hubs Trigger を使ってみました。
コードは単純で、流れてきたメッセージをログに書き出すだけです。
書いたコードを保存すると自動的に実行が開始されるので、設定が上手くいっていれば Logs に流れてきた JSON が表示されていきます。
あとは Json.NET などを使ってデシリアライズすれば、EventType などで処理を分けることも出来そうです。
Azure Functions を使えばデータを DocumentDB や Azure Table に追加できるので、DocumentDB に格納したデータを更に Azure Search に投入して検索といったことも可能になるのではないかと。
思った以上に Azure Functions が便利なので、いろんな場所で使ってみたいと思いました。