Dynamics CRM 2011 : 監査の記録を SDK で取得する


みなさん、こんにちは。

今回も管理者と開発者ともに関連する内容として、SDK を利用した
監査ログの出力を紹介します。

概要

Microsoft Dynamics CRM 2011 では、レコード操作の監査機能が
あります。これはレコードの作成、更新、削除を監査できる機能で
その結果は画面より確認が可能です。

しかしエクセルにエクスポートやレポートに出力といった機能には
対応しないことから、データを外部に出力したい場合には SDK を
利用した開発が必要となります。

今回は SDK で提供されるいくつかの機能を組み合わせて、監査
ログの出力を行ってみます。

監査ログ取得のサンプル

監査ログ本体の取得

今回は QueryExpression を利用して監査ログの一覧を取得して
みます。またログの種類を指定し、作成、更新、削除のログのみ
取得してみます。この際ヘルパーコードの optionsets.cs を利用
すれば、容易に指定が可能です。

// QueryExpression を利用して監査を取得
QueryExpression qe = new QueryExpression()
{
    // エンティティ名として Audit を指定
    EntityName = Audit.EntityLogicalName,
    // 条件として Action を指定
    Criteria =
    {
        // 特定のイベントのみ取得
        Conditions = { new ConditionExpression("action", ConditionOperator.In, new int[]{
            // optionset.cs を利用したオプションセットの指定
            (int)AuditAction.Update,
            (int)AuditAction.Create,
            (int)AuditAction.Delete})}
    },
    Orders =
    {new OrderExpression("createdon", OrderType.Descending)}                   
};

// RetrieveMultipleRequest でデータを取得
RetrieveMultipleRequest retrieveMultipleRequest = new RetrieveMultipleRequest()
{
    Query = qe
};

EntityCollection retrieveMultipleResponse =
((RetrieveMultipleResponse)_serviceProxy.Execute(retrieveMultipleRequest)).EntityCollection;

AuditDetail の取得

監査ログ本体を取得したら、ログの詳細を取得します。

// 取得した監査より AuditDetail を取得
foreach (Audit au in retrieveMultipleResponse.Entities)
{
    RetrieveAuditDetailsRequest request = new RetrieveAuditDetailsRequest();
    request.AuditId = (Guid)au.AuditId;

    RetrieveAuditDetailsResponse response = (RetrieveAuditDetailsResponse)_serviceProxy.Execute(request);
    // 取得した response に含まれる AuditDetails を 表示
    DisplayAuditDetails(response.AuditDetail);

AuditDetail の表示

最後に取得した監査ログの詳細を画面に出力します。
監査ログには種類があるため、フィールドの監査をしている点や
以前の情報または新しい情報を持っているか確認します。また
フィールドによっては文字列以外の型を持っているため、適切に
処理する必要があります。

private static void DisplayAuditDetails(AuditDetail detail)
{
    // 監査ログの詳細より監査ログ本体を再度取得。 
    Audit record = (Audit)detail.AuditRecord;

    // 監査ログ本体のプロパティより情報を出力
    Console.WriteLine("\n監査レコード作成日時: {0}", record.CreatedOn.Value.ToLocalTime());
    Console.WriteLine("エンティティ: {0}, イベント: {1}, 操作: {2}",
        record.ObjectId.LogicalName, record.FormattedValues["action"],
        record.FormattedValues["operation"]);

    // 監査ログの詳細より種類を取得
    var detailType = detail.GetType();
    // フィールドの監査だった場合は情報を出力
    if (detailType == typeof(AttributeAuditDetail))
    {
        // 監査されたフィールドの情報を取得
        var attributeDetail = (AttributeAuditDetail)detail;

        // 新しい値がある場合
        if (attributeDetail.NewValue != null)
        {
            foreach (KeyValuePair<String, object> attribute in attributeDetail.NewValue.Attributes)
            {
                String oldValue = "(値なし)", newValue = "(値なし)";

                // 以前の値もあれば表示
                if (attributeDetail.OldValue.Contains(attribute.Key))
                {
                    // 以前の値を取得
                    oldValue = attributeDetail.OldValue[attribute.Key].ToString();
                            
                    // 文字列型でない場合、相当する CRM の型に変更して値を取得
                    if (oldValue == "Microsoft.Xrm.Sdk.Money")
                        oldValue = ((Microsoft.Xrm.Sdk.Money)attributeDetail.OldValue[attribute.Key]).Value.ToString();
                    else if (oldValue == "Microsoft.Xrm.Sdk.OptionSetValue")
                        oldValue = ((Microsoft.Xrm.Sdk.OptionSetValue)attributeDetail.OldValue[attribute.Key]).Value.ToString();
                    // 参照型の場合、参照先のレコードがすでにない可能性があるため、null チェック必須
                    else if (oldValue == "Microsoft.Xrm.Sdk.EntityReference")
                        oldValue = ((Microsoft.Xrm.Sdk.EntityReference)attributeDetail.OldValue[attribute.Key]).Name == null ?
                            "使用できないレコード" : ((Microsoft.Xrm.Sdk.EntityReference)attributeDetail.OldValue[attribute.Key]).Name.ToString();
                }

                // 新しい値を取得
                newValue = attributeDetail.NewValue[attribute.Key].ToString();

                // 文字列型でない場合、相当する CRM の型に変更して値を取得
                if (newValue == "Microsoft.Xrm.Sdk.Money")
                    newValue = ((Microsoft.Xrm.Sdk.Money)attributeDetail.NewValue[attribute.Key]).Value.ToString();
                else if (newValue == "Microsoft.Xrm.Sdk.OptionSetValue")
                    newValue = ((Microsoft.Xrm.Sdk.OptionSetValue)attributeDetail.NewValue[attribute.Key]).Value.ToString();
                // 参照型の場合、参照先のレコードがすでにない可能性があるため、null チェック必須
                else if (newValue == "Microsoft.Xrm.Sdk.EntityReference")
                    newValue = ((Microsoft.Xrm.Sdk.EntityReference)attributeDetail.NewValue[attribute.Key]).Name == null ?
                        "使用できないレコード" : ((Microsoft.Xrm.Sdk.EntityReference)attributeDetail.NewValue[attribute.Key]).Name.ToString();

                Console.WriteLine("フィールド: {0}, 以前の値: {1}, 新しい値: {2}",
                    attribute.Key, oldValue, newValue);
            }
        }

        if (attributeDetail.OldValue != null)
        foreach (KeyValuePair<String, object> attribute in attributeDetail.OldValue.Attributes)
        {
            // 新しい値があれば、すでに古い値の処理も終わっているため処理をスキップ
            if (attributeDetail.NewValue == null)
            {
                // 新しい値がないことが分かってるため、値なしを設定
                String newValue = "(値なし)";
                
                // 以前の値を取得                               
                String oldValue = attributeDetail.OldValue[attribute.Key].ToString();
                // 文字列型でない場合、相当する CRM の型に変更して値を取得
                if (oldValue == "Microsoft.Xrm.Sdk.Money")
                    oldValue = ((Microsoft.Xrm.Sdk.Money)attributeDetail.OldValue[attribute.Key]).Value.ToString();
                else if (oldValue == "Microsoft.Xrm.Sdk.OptionSetValue")
                    oldValue = ((Microsoft.Xrm.Sdk.OptionSetValue)attributeDetail.OldValue[attribute.Key]).Value.ToString();
                // 参照型の場合、参照先のレコードがすでにない可能性があるため、null チェック必須
                else if (oldValue == "Microsoft.Xrm.Sdk.EntityReference")
                    oldValue = ((Microsoft.Xrm.Sdk.EntityReference)attributeDetail.OldValue[attribute.Key]).Name == null ?
                        "使用できないレコード" : ((Microsoft.Xrm.Sdk.EntityReference)attributeDetail.OldValue[attribute.Key]).Name.ToString();

                Console.WriteLine("フィールド: {0}, 以前の値: {1}, 新しい値: {2}",
                    attribute.Key, oldValue, newValue);
            }
        }
    }
    Console.WriteLine();

その他の考慮点

今回はレコードの作成、更新、削除イベントのみを取得し、結果を表示
していますが、他イベントを取得した場合にどのように出力するか、また
今回処理していない型が存在する場合の対処なども必要になります。

オプションセット、フィールド名、エンティティ名は値が取得できるだけ
なので、ラベルの表示が必要な場合は、メタデータに対するクエリが
必要になります。

まとめ

監査記録は様々な情報を保持していますので、出力するとしても
内容によって処置が異なる点検討が必要となります。今回紹介
したサンプルコードはすべて SDK にある情報のみで作成しています
ので、メソッドの詳細やクラスの詳細が不明な場合は、是非最新の
SDK をご確認ください。

‐ Dynamics CRM サポート 中村 憲一郎

Comments (4)

  1. mogu__mogu より:

    こんにちわ、いつも新しい話題をご提供頂き、ありがとうございます。

    監査の機能ですが、Dynamics CRM の Plugin 以外の実装方法では、

    SQL Server の Audit 機能で利用できないかと思っております。

    Plugin を利用した場合と、SQL Server の Audit 機能を使った場合のメリットデメリット

    があれば、教えて頂きたいです。

  2. コメントをいただき、ありがとうございます。

    Microsoft Dynamics CRM 上での監査と Microsoft SQL Server 上の監査については取得できる内容や情報の保存先、参照方法に違いがありますので、要件次第であると考えています。

    Microsoft Dynamics CRM の監査を利用した場合、設定から記録の閲覧まですべてブラウザ上から行えますが、その一方でより詳細なデータベースレベルのログを行うことが出来ません。

    一方で Microsoft SQL Server を利用した場合、データベースレベルではより詳細かつ柔軟な情報が取得できますが、Microsoft Dynamics CRM レコードやユーザの紐づけ、値の意味等すべて解決する必要がございます。

    参考になりますでしょうか。

    中村 憲一郎

  3. mogu__mogu より:

    ご返信ありがとうございます。

    要件で使い分けということですね。

    SQL Server の Audit で実装してしまうと、エンドユーザーの管理者には、それを参照するための画面等を

    提供する必要性が生じそう。

    Dynamics CRM の Plugin を使えば、DCRM から参照可能なため、特別にUIを作成する必要が

    なく、工数的には、こちらの方がよさそうに思いました。

    ただ、監査ログとなりますと、データ件数が膨大になり、DCRM上のエンティティ自体に持って良いものかどうか

    迷ってしまします。(データを定期的に、吐き出すようなものが必要なのかと思ったり。)

    あと、気になる部分では、SQL Server Audit と、DCRM の Plugin でどちらがパフォーマンスがよいかどうか。

    監査機能を実装したために、パフォーマンスが急激に劣化すると考えものですので。

    頂いたご意見を参考に、どちらが良いか、もう少し悩んでみます。

    ありがとうございました。

  4. コメントをいただき、ありがとうございます。

    パフォーマンスは考慮すべき点ですが、Microsoft Dynamics CRM と Microsoft SQL Server の監査で監査対象に差異がある点、またその処理方式、実際にリソースを使用する箇所が異なる点がポイントであると考えます。

    ただストレージの観点は確かに重要で、Microsoft Dynamics CRM の監査を利用した場合、必然的に組織データベースの容量が増加し、バックアップ運用などにも影響が出ます。

    監査についてはその性質上、Microsoft SQL Server Enterprise Edition をご利用の場合に、パーティション機能を自動で利用しますので、その点も考慮対象としてください。

    よろしくお願いいたします。

    中村 憲一郎

Skip to main content