銀の光と碧い空

クラウドなインフラとC#なアプリ開発の狭間にいるエンジニアの日々

CloudWach (Windows版)に.NET アプリから独自のイベントログとパフォーマンスカウンタを送信する方法

このエントリは、AWS Advent Calendar 3日目の記事です

AWS Advent Calendar 2014 - Qiita

先日出た CloudWatch にWindows Serverからイベントログやパフォーマンスカウンタの値を送信できる機能ですが、これを使うことによって、アプリケーションから独自のイベントログやパフォーマンスカウンタを定義し送信することができます。ただ、若干面倒なところがあるのでブログにまとめてみました。

事前準備

Ec2Configは念の為最新の2.2.11 を使っています*1。Windows Serverは最新の Windows Server 2012 R2。アプリケーションはC# で書かれた.NET アプリを想定していますが、対応するWindowsのAPIを利用すれば他言語/Frameworkでも問題ないと思います。

イベントログを送る

独自のイベントログを定義する(理由編)

AWSのドキュメントの手順のところにあるのですが、Ec2Configが送信するイベントログは、イベントビューアー上で確認できる状態になっていないといけません。一方、Windows においてイベントビューアーに表示されるようなイベントログを定義するのは割と面倒です。というのも、最近のWindowsでは、イベントログとは別のETW(Event Tracing for Windows)という仕組みがが使われるようになっています。このETWは、.NET標準のAPIが用意されているため、C#/VBといった managed なコードから利用することが簡単になっている反面、標準の機能ではイベントビューアーに表示されません*2

ではどうするかというと、ETWについているChannel Supportという機能を使います。ChannelをサポートしたETWのログはイベントビューアーに表示されます。が、このChannelの機能、.NET標準のAPIではサポートされていません。その代わり、BCLチームが作成した Microsoft.Diagnostics.Tracing.EventSource ライブラリを使うと、事前準備が必要ですが、ManagedコードからChannelサポートしたETWのログを書きこめるようになります。

独自のイベントログを定義する(手順編)

1: ログを送信したいアプリ(コンソールなりWPFなりASP.NETなり)に、Microsoftほげもげライブラリの参照を追加します。Nugetからどうぞ。ダウンロードすると、docx なファイルも一緒に追加されるのですが、使い方はこれが一番詳しいです。

Install-Package Microsoft.Diagnostics.Tracing.EventSource -Version 1.0.26

2: ログを送信するクラス・メソッドを定義します。もともと構造化ログをサポートする意味合いもあったためログのメソッドに意味合いをもたせるのですが、単純なサンプルとしてはこうなります。

using Microsoft.Diagnostics.Tracing;

namespace MyEventSource
{
    [EventSource(Name = "Sample-EventLog")]
    public sealed class MyEventSource : EventSource
    {
        public static MyEventSource Log = new MyEventSource();

        [Event(1, Message = "{0}", Channel = EventChannel.Admin)]
        public void Debug(string message)
        {
            WriteEvent(1, message);
        }
    }
}

3: ビルドします。すると *.etwManifest.dll とか *.etwManifest.man とかいう見慣れないファイルが生成されているはずです。これが Microsoft.Diagnostics.Tracing.EventSource ライブラリの便利なところで、ChannelをサポートしたETWはOSに登録しないといけないのですが、そのために必要なファイルを生成してくれます。

f:id:tanaka733:20141201232051p:plain

4: OSに登録します。実際にログを送信するEC2インスタンス上に3で生成された2つのファイルを持ってきて、以下のコマンドを管理者権限で実行します。なお、rf と mfの後にしているdllはフルパスが必要です。

> wevtutil im "D:\Documents\visual studio 2013\Projects\MyEventSource\MyEventSource\bin\Debug\MyEventSource.Sample-EventLog.etwManifest.man"  rf:"D:\Documents\visual studio 2013\Projects\MyEventSource\MyEventSource\bin\Debug\MyEventSource.Sample-EventLog.etwManifest.dll" /mf:"D:\Documents\visual studio 2013\Projects\MyEventSource\MyEventSource\bin\Debug\MyEventSource.Sample-EventLog.etwManifest.dll"

5: 念のためイベントビューアーで確認してみましょう

f:id:tanaka733:20141201232401p:plain

6: Ec2Configの設定ファイルに追加してみます。PrametersのLogName は下記のように EventSourceの名前/Channelの名前 で指定します。

{
    "Id": "SampleEventLog",
    "FullName": "AWS.EC2.Windows.CloudWatch.EventLog.EventLogInputComponent,AWS.EC2.Windows.CloudWatch",
    "Parameters": {
        "LogName": "Sample-EventLog/Admin",
        "Levels": "7"
    }
},

7: Ec2Config サービスを再起動後、ログのAPIを呼びだすメソッドを実行すると、CloudWatchLogs に送信されているはずです。

パフォーマンスカウンタの値を送る

1: カスタムパフォーマンスカウンタを定義する。

MSDNに説明が載っています。ただ、複数サーバーに展開するときなどを考えるとC#で書いてビルドしたexeを実行する、というのは若干面倒なこともあるので、PowerShell版のサンプルコードを載せておきます*3

# Perfmon Name and Help
$categoryName = "MyAppPerformance"
$categoryHelp = "MyApp"
$categoryType = [System.Diagnostics.PerformanceCounterCategoryType]::MultiInstance

# Fix it by Index
$counterName = "Count", "Time"
$counterType = "NumberOfItems32", "NumberOfItems32"
$counterHelp = "Count", "Time"

# Delete Existing Category
$categoryCount = ([System.Diagnostics.PerformanceCounterCategory]::GetCategories() | where CategoryName -eq $categoryName).Count
If ($categoryCount -ne 0){ [System.Diagnostics.PerformanceCounterCategory]::Delete($categoryName) }

# Create Collection
$objCCDC = New-Object System.Diagnostics.CounterCreationDataCollection
0..($counterName.count -1) `
| %{
    $objCCD = New-Object System.Diagnostics.CounterCreationData
    $objCCD.CounterName = $counterName[$_]
    $objCCD.CounterType = $counterType[$_]
    $objCCD.CounterHelp = $counterHelp[$_]
    $objCCDC.Add($objCCD) > $null
}
$objCCDC | Format-Table -AutoSize | Out-String | %{[Console]::WriteLine($_)}

# Perfmon Execute
[System.Diagnostics.PerformanceCounterCategory]::Create($categoryName, $categoryHelp, $categoryType, $objCCDC) | Out-String | %{[Console]::WriteLine($_)}

2: アプリでカスタムパフォーマンスカウンタに送信する処理を書く

こちらも最低限のサンプルを載せておきます。

private static readonly string categoryName = "MyAppPerformance";
private static readonly string countCounterName = "Count";
private static readonly string timeCounterName = "Time";


public void Write(PerformanceData data)
{
    var retryCounter = new PerformanceCounter(categoryName, countCounterName, data.EventName, false);
    var elaspedCounter = new PerformanceCounter(categoryName, timeCounterName, data.EventName, false);
    retryCounter.RawValue = data.Count;
    elaspedCounter.RawValue = data.Time;
}

3: 設定用のjsonに追記する。

{
    "Id": "PerformanceCounterMyAppCount",
    "FullName": "AWS.EC2.Windows.CloudWatch.PerformanceCounterComponent.PerformanceCounterInputComponent,AWS.EC2.Windows.CloudWatch",
    "Parameters": {
        "CategoryName": "MyAppPerformance",
        "CounterName": "Count",
        "InstanceName": "instance1",
        "MetricName": "MyAppPerformanceCount",
        "Unit": "Count",
        "DimensionName": "Host",
        "DimensionValue": "{hostname}"
    }
},
{
    "Id": "PerformanceCounterMyAppTime",
    "FullName": "AWS.EC2.Windows.CloudWatch.PerformanceCounterComponent.PerformanceCounterInputComponent,AWS.EC2.Windows.CloudWatch",
    "Parameters": {
        "CategoryName": "MyAppPerformance",
        "CounterName": "Time",
        "InstanceName": "instance1",
        "MetricName": "MyAppPerformanceTime",
        "Unit": "Count",
        "DimensionName": "Host",
        "DimensionValue": "{hostname}"
    }
},

MetricName はCloudWatch から見てわかりやすい名前にしましょう。またUnitもCloudWatchで使える値の中から適したものを選んでおきます。 また、Dimensionが1組だけ指定できます*4。1つだけなので厳選する必要がありますが、複数台スケーリングさせているWebサーバー上のパフォーマンスカウンタであれば、ホスト名でDimensionを作っておくのがいいかと思います*5

4: アプリを実行して、CloudWatchに送信されていることを確認する。

まとめ

というわけで、AWS といいつつ、ほぼC# とPowerShellな内容になってしまいました。が、内容はC# と PowerShell でも、これを使うことによってシステムから出力されるイベントログやパフォーマンスカウンタだけでなく、自前のアプリのログやパフォーマンスカウンタをCloudWatchとCloudWatchLogsで管理できるようになりました。

この機能の一番のメリットは、CloudWatchとCloudWatchLogsというAWSのパワフルなツールを活用していますが、ログやパフォーマンスカウンタを送信するアプリ側はAWSのことを一切知らない点です。アプリだけであれば、オンプレミスでもAzuer上でも動きます。そして、ETWもしくはEventLogおよびパフォーマンスカウンタという標準的なツールでそのログやカウンタの監視もできます。Ec2Configサービスがその間の橋渡しをしてくれているのです。

なお、弊社ではアプリは別の監視サービスで監視していて、このCloudWatchとCloudWatchLogsはそのサービスでは監視しづらい、Webサーバーの常駐させる自前のWindowsサービスの監視に利用しています。

と持ち上げたところでちょっと下げておくと、まだこなれていない部分もあります。パフォーマンスカウンタのデータを取る頻度をもっと多くしてほしかったり、 設定用のjsonで {ip_address} という置換変数だけ置換されなかったり、Dimensionが1つしか指定できなかったりします。このあたりは、今後のアップデートに期待しています。

PS

Windows パフォーマンスカウンタをCloudWatch に送信しようとすると、設定用の json ファイルがかなり肥大化してきて、手書きではなかなかつらいです。

というわけで、弊社ではExcel VSTO を使って、Excel上で必要なパフォーマンスカウンタ一覧や、イベントログなどを指定すると、設定用のjsonを吐き出すツールを私が作っています。

この辺はきっとAWS側でのなにかしらの対応してくれるとは思っていたりします...

*1:バージョンの確認とサイレントインストールがめんどいです

*2:なので、ETWのログを見るためには別のツールを利用する

*3:PowerShellという名のC#コードという噂もある

*4:本来のPutCustomMetrics なら10組までいけるはずですが...

*5:ip_address は現バージョンでは動きません