- PR -

アプリケーションの操作ログ出力の実装方法

投稿者投稿内容
indigo-x
大ベテラン
会議室デビュー日: 2008/02/21
投稿数: 207
お住まい・勤務地: 太陽の塔近く
投稿日時: 2009-02-02 18:19
気になるところ。。。
  Windowsアプリでしょうか?ASP.NETでしょうか?Windows+Service?
    (層の分離の仕方も若干かわるかと。。)

  ログはサーバーで出力でしょうか?(多分サーバーと思いますが。。)
  あと出力はファイルorDB?(ファイルっぽいが。。)
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2009-02-03 01:51
引用:

かつひとさんの書き込み (2009-02-02 17:32) より:
ですので、操作( = クラスメソッドでも良いと思います)に加えて、誰が操作したか(ユーザー名)が必須の記録になると考えています。


引用:

かつひとさんの書き込み (2009-02-02 17:32) より:
//アプリ情報を管理するクラス
public class AppInfo
{
 //ログインしているユーザー名を取得
 public static GetLoginUserName()
 {
  ・・・
 }
}

//ドメインクラス
public class Book
{
 public Book Get(string id)
 {
  Book book = new Book();
  ・・・
  Log.Write(DateTime.Now, AppInfo.GetLoginUserName, "Book.Get"・・・);
  return book;
 }
}


目的はセキュリティーということであり、ユーザーが主体になるのだろうとは思います。しかし、それはあくまでも要求仕様で優先順位が高いだけであり、別段ユーザーの扱いだけを特別扱いするのは、システムをむしろ複雑なものにするのではないかと私は考えます。

たとえば、ソフトウェアの教科書や試験問題の例に良くあるような、図書館の貸し出しシステムだと、貸し出す本と、借りるユーザーは、多対多で関連付けられています。この関連付けは本とユーザーで対等です。

ドメインクラスとしては Book クラスを作られるのならば、それと対等に User クラスもあるべきです。そして、図書館に蔵書の Book がある・貸し出されている、という管理をするのと同様に、図書館に登録された User が居る・図書館に入ってレジ(というかカウンター)で会員カードを提示した、という管理をすべきです。
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2009-02-03 01:57
引用:

かつひとさんの書き込み (2009-02-02 17:32) より:
//アプリ情報を管理するクラス
public class AppInfo
{
 //ログインしているユーザー名を取得
 public static GetLoginUserName()
 {
  ・・・
 }
}


これを拝見すると、おそらくユーザーの管理は、OS か Web アプリケーションの API で取得できるユーザーのログイン状態をそのまま流用する、ということなのかなと推測します。ただ、この場合であっても、前述のような User クラスが本来はあったはずだが、それを端折っている、というということを意識して作ったほうが素直なアプリケーションになると思います。
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2009-02-03 02:08
引用:

かつひとさんの書き込み (2009-02-02 17:32) より:
//アプリ情報を管理するクラス
public class AppInfo
{
 //ログインしているユーザー名を取得
 public static GetLoginUserName()
 {
  ・・・
 }
}


引用:

かつひとさんの書き込み (2009-02-02 17:32) より:
 ドメイン層(汎用)にアプリケーション固有メソッド呼び出しが入ってしまう点が気になるのですが、そこまで気にする必要はないのでしょうか。


前述の続きでの考えによれば、この AppInfo クラスは、Book と対等の立場の層のクラスであると捉えられると思います。ですからそれ自体は問題ないと思います。

ログ書き込みのタイミングを、呼ばれる中に書くか外に書くかという問題がありますが、これは好みの問題かもしれませんが、私だったら、「ログ」と言うものも一種の UI だと考えて、なるべく層の外側で書きたいです。内側に書くほうが、コーディング漏れがなくて良い、という考え方もありますが、逆に、ある条件の時だけ書きたいというような、ある種のユーザーインターフェースを意識するような場合もあるので、やっぱり外で書きたいです。
まりも
ベテラン
会議室デビュー日: 2006/08/19
投稿数: 77
投稿日時: 2009-02-03 20:50
ログをUIだと設計しても間違っているとは思いませんが、
でもそうすると、UI層を2セット作るような、
大仰な話になってしまうのではないかなあと思います。

ログとはそこまでして出力するものでしょうか?
かつひと
常連さん
会議室デビュー日: 2006/06/01
投稿数: 32
投稿日時: 2009-02-04 13:21
 皆様、様々なご意見頂きありがとうございます。
そもそも私の質問の仕方が曖昧であり、申し訳ありませんでした。

 お聞きしたいことは、表題にも書きました通り、操作ログを出力する実装方法やその考え方です。
トレースやデバッグのためのログは、今回は除外ということで質問させて頂きたく思います。

 機能要求あるいは非機能要求で操作ログ出力が明記されている、または法的な理由(セキュリティ)etcで実装を余儀なくされていると仮定します。
ですので、「ログ出力がそもそも必要か」という議論は興味深い内容ではありますが、今回は範囲外とさせてください。

 操作ログですので、誰が、いつ、どんな操作をしたか、という情報を残す必要があると考えます。

 unibon様の言われるように、私もUI層に近い処理だと思います。
そこで問題になると感じている(実際に問題になっていますが汗)のが、

・開発者の仕様理解度・技術レベルよって、実装したりしなかったりする。
  unibon様の文章をお借りしますと、漏れる可能性があるということです。
・以下のようなハードコーティング的なコードが散在、保守困難になる。
  例) Log.Write(userName + "△△情報を検索しました。");

 では「ドメイン層の共通クラス内で実装すれば良いのか」と考えますと、漏れはなくなると思いますが、これもunibon様がご指摘のように、○○のときだけログを出したいという制御ができないと思います。

 質問者の立場で勝手なことを申しておりますが、どうかご了承ください。
アドバイスを頂ければと思います。
よこけん
大ベテラン
会議室デビュー日: 2006/01/31
投稿数: 216
投稿日時: 2009-02-04 14:25
ログはどのレベルで取りたいのでしょうか?
ドメインモデルの操作レベルで取りたいならドメイン層、
アプリケーションの機能単位で取りたいならアプリケーション層、
ってのが素直だと思うのですが…。

> ○○のときだけログを出したいという制御ができないと思います。

その必要性があるのですか?
あるとしたら、それはどういうルールに従って制御するのですか? (抽象的なルールとして表現できますか?)
そもそも、「○○のときだけ」という制御ができないということは、ログを取るレベルに対する配置場所が不適切だからだと思います。

_________________
C#と諸々
セラフ
ベテラン
会議室デビュー日: 2005/12/01
投稿数: 95
お住まい・勤務地: 東北の顔の形といえば
投稿日時: 2009-02-04 15:16
UI層のログといわれると、下記のようなものを想像してしまいます。
※ボタンがクリックされたことやテキストが変更されたなどの操作だけを記録する。(ソース見てください。)

本来は、ButtonやTextBoxなどの主なUI部品を継承し、OnXXXXメソッドをオーバーライドして、そこに適切なログメッセージを埋め込むのがUI層のログだと考えます。しかし、小さな案件ではそこまでやってもたいした効果は得られないので、提示したコードのように手抜き実装してしまいます。

UI層のログとは、こう言った完全に操作だけを記録するものだと私は考えますので、"△△情報を検索しました。"のようなログは、UI層では無いようにおもいます。

っということで、よこけんさんのおっしゃるように、
引用:

ドメインモデルの操作レベルで取りたいならドメイン層、
アプリケーションの機能単位で取りたいならアプリケーション層、
ってのが素直だと思うのですが…。


に同意します・・・

コード:
Public Class TestForm
    Inherits MasterForm

    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        Try
            If disposing AndAlso components IsNot Nothing Then
                components.Dispose()
            End If
        Finally
            MyBase.Dispose(disposing)
        End Try
    End Sub

    Friend WithEvents Button1 As System.Windows.Forms.Button

    Private components As System.ComponentModel.IContainer

    Private Sub InitializeComponent()
        Me.Button1 = New System.Windows.Forms.Button
        Me.SuspendLayout()
        '
        'Button1
        '
        Me.Button1.Location = New System.Drawing.Point(78, 12)
        Me.Button1.Name = "Button1"
        Me.Button1.Size = New System.Drawing.Size(75, 23)
        Me.Button1.TabIndex = 0
        Me.Button1.Text = "Button1"
        Me.Button1.UseVisualStyleBackColor = True
        '
        'TestForm
        '
        Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 12.0!)
        Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
        Me.ClientSize = New System.Drawing.Size(165, 46)
        Me.Controls.Add(Me.Button1)
        Me.Name = "TestForm"
        Me.Text = "TestForm"
        Me.ResumeLayout(False)

    End Sub

    Public Sub New()
        Me.InitializeComponent()
    End Sub

End Class

Public Class MasterForm
    Inherits Form

    Private Const TemplateString As String = "{0}が{1}されました。"

    Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)

        Debug.WriteLine(String.Format(MasterForm.TemplateString, Me.Text, "Load"))

        For Each targetControl As Control In Me.Controls
            If TypeOf targetControl Is Button Then
                AddHandler targetControl.Click, AddressOf Me.Button_Click
            End If
        Next

        MyBase.OnLoad(e)

    End Sub

    Private Sub Button_Click(ByVal sender As Object, ByVal e As System.EventArgs)
        Dim tempButton As Button = Nothing
        If TypeOf sender Is Button Then
            tempButton = DirectCast(sender, Button)
            Debug.WriteLine(String.Format(MasterForm.TemplateString, tempButton.Text, "Click"))
        End If
    End Sub

End Class

スキルアップ/キャリアアップ(JOB@IT)