システム間のマクロにおけるログの利用

12506 ワード

この記事では、システム間のアイリスでマクロに基づいたロギングシステムを設計し、構築します.いくつかinfo on macros .
ログ記録システム
ロギングシステムは、デバッグや監視中に多くの時間を節約するアプリケーションの作業を監視するための便利なツールです.私たちのシステムは2つの部分から成ります.
  • ストレージクラス(ログレコード用)
  • ログに新しいレコードを自動的に追加するマクロの集合
  • ストレージクラス
    コンパイル中に、または実行時にこのデータが得られるときに指定するテーブルを作成しましょう.システムのマクロの2番目の部分で作業する際には、必要に応じてコンパイル時にログ処理可能な詳細を持つことを目指します.
    インフォメーション
    の間
    イベントタイプ
    編集
    クラス名
    編集
    メソッド名
    編集
    メソッドに渡される引数
    編集
    CLSソースコードの行番号
    ランタイム
    生成されたintコードの行番号
    ランタイム
    ユーザー名
    ランタイム
    日付・時刻
    ランタイム
    メッセージ
    ランタイム
    IPアドレス
    ランタイム
    アプリを作成しましょう.上記の表からプロパティを含むログクラスです.ときにアプリ.ログオブジェクトは、ユーザー名、日付/時刻とIPアドレスのプロパティが自動的に記入され、作成されます.
    アプリ.ログクラス:
    Class App.Log Extends %Persistent
    
    {
    
    /// Type of event
    Property EventType As %String(MAXLEN = 10, VALUELIST = ",NONE,FATAL,ERROR,WARN,INFO,STAT,DEBUG,RAW") [InitialExpression = "INFO"];
    
    /// Name of class, where event happened
    Property ClassName As %Dictionary.Classname(MAXLEN = 256);
    
    /// Name of method, where event happened
    Property MethodName As %String(MAXLEN = 128);
    
    /// Line of int code
    Property Source As %String(MAXLEN = 2000);
    
    /// Line of cls code
    Property SourceCLS As %String(MAXLEN = 2000);
    
    /// IRIS user
    Property UserName As %String(MAXLEN = 128) [InitialExpression = {$username}];
    
    /// Arguments' values passed to method
    Property Arguments As %String(MAXLEN = 32000, TRUNCATE = 1);
    
    /// Date and time
    Property TimeStamp As %TimeStamp [InitialExpression = {$zdt($h, 3, 1)}];
    
    /// User message
    Property Message As %String(MAXLEN = 32000, TRUNCATE = 1);
    
    /// User IP address
    Property ClientIPAddress As %String(MAXLEN = 32) [InitialExpression = {..GetClientAddress()}];
    
    /// Determine user IP address
    ClassMethod GetClientAddress()
    {
      // %CSP.Session source is preferable
      #dim %request As %CSP.Request
      If ($d(%request)) {
        Return %request.CgiEnvs("REMOTE\_ADDR")
      }
      Return $system.Process.ClientIPAddress()
    }
    
    }
    
    ログのマクロ
    通常、マクロは別の*に格納されます.incの定義を含むファイル.必要なファイルはinclude macrofilenameコマンドを使用してクラスに含めることができます.ログマクロ.
    まず、ユーザーがアプリケーションのコードに追加するメインマクロを定義します.
    #define LogEvent(%type, %message) Do ##class(App.Log).AddRecord($$$CurrentClass, $$$CurrentMethod, $$$StackPlace, %type, $$$MethodArguments, %message)
    
    このマクロはイベントタイプとメッセージの2つの入力引数を受け付けます.message引数はユーザによって定義されますが、イベント型パラメーターには、イベントタイプを自動的に識別する別の名前のマクロが必要です.
    #define LogNone(%message) $$$LogEvent("NONE", %message)
    
    #define LogError(%message) $$$LogEvent("ERROR", %message)
    
    #define LogFatal(%message) $$$LogEvent("FATAL", %message)
    
    #define LogWarn(%message) $$$LogEvent("WARN", %message)
    
    #define LogInfo(%message) $$$LogEvent("INFO", %message)
    
    #define LogStat(%message) $$$LogEvent("STAT", %message)
    
    #define LogDebug(%message) $$$LogEvent("DEBUG", %message)
    
    #define LogRaw(%message) $$$LogEvent("RAW", %message)
    
    したがって、ログを実行するためには、ユーザーはアプリケーションコードで$$$ logerror(“追加メッセージ”)マクロを配置する必要があるだけです.
    私たちが今する必要があるのは、$ $ currentClass、$$$ CurrentMethod、$$$ Stackplace、$$$ method議論マクロを定義することです.最初の3つから始めましょう.
    #define CurrentClass ##Expression($$$quote(%classname))
    
    #define CurrentMethod ##Expression($$$quote(%methodname))
    
    #define StackPlace $st($st(-1),"PLACE")
    
    %classname , %methodname 変数はdocumentation . The $stack 関数はintコード行番号を返す.CLS行番号に変換するにはcode .
    %引数のパッケージを使用してメソッド引数とその値のリストを取得しましょう.これは、メソッドの説明を含むクラスについてのすべての情報が含まれます.特に辞書に興味があります.CompiledMethodクラスとその形式のプロパティをリスト化します.
    $lb($lb("Name","Classs","Type(Output/ByRef)","Default value "),...)
    
    メソッドシグネチャに対応します.例えば、
    ClassMethod Test(a As %Integer = 1, ByRef b = 2, Output c)
    
    の値は以下の形式になります:
    $lb(
      $lb("a","%Library.Integer","","1"),
      $lb("b","%Library.String","&","2"),
      $lb("c","%Library.String","*","")
    )
    
    我々は作る必要がある$$$MethodArguments マクロが次のコード(テストメソッド用)に展開します.
    "a="_$g(a,"Null")_"; b="_$g(b,"Null")_"; c="_$g(c,"Null")_";"
    
    これを実現するには、コンパイル中に次の手順を実行する必要があります.
  • クラス名とメソッド名を取得する
  • の対応するインスタンスをオープンする%Dictionary.CompiledMethod クラスと取得FormalSpec プロパティ
  • ソースコードラインに変換
  • アプリに対応するメソッドを追加しましょう.ログクラス:
    ClassMethod GetMethodArguments(ClassName As %String, MethodName As %String) As %String
    {
        Set list = ..GetMethodArgumentsList(ClassName,MethodName)
        Set string = ..ArgumentsListToString(list)
        Return string
    }
    
    ClassMethod GetMethodArgumentsList(ClassName As %String, MethodName As %String) As %List
    {
        Set result = ""
        Set def = ##class(%Dictionary.CompiledMethod).%OpenId(ClassName _ "||" _ MethodName)
        If ($IsObject(def)) {
            Set result = def.FormalSpecParsed
        }
        Return result
    }
    
    ClassMethod ArgumentsListToString(List As %List) As %String
    {
        Set result = ""
        For i=1:1:$ll(List) {
            Set result = result _ $$$quote($s(i>1=0:"",1:"; ") _ $lg($lg(List,i))_"=")
            _ "_$g(" _ $lg($lg(List,i)) _ ","_$$$quote(..#Null)_")_"
            _$s(i=$ll(List)=0:"",1:$$$quote(";"))
        }
        Return result
    }
    
    さあ、定義しましょう$$$MethodArguments マクロとして:
    #define MethodArguments ##Expression(##class(App.Log).GetMethodArguments(%classname,%methodname))
    
    ユースケース
    次に、作成しましょうApp.Use クラスATest ログ記録システムの機能を実証する方法
    Include App.LogMacro
    Class App.Use [ CompileAfter = App.Log ]
    {
    /// Do ##class(App.Use).Test()
    ClassMethod Test(a As %Integer = 1, ByRef b = 2)
    {
        $$$LogWarn("Text")
    }
    }
    
    その結果、$$$LogWarn("Text") INTコードのマクロが次の行に変換されます.
    Do ##class(App.Log).AddRecord("App.Use","Test",$st($st(-1),"PLACE"),"WARN","a="\_$g(a,"Null")\_"; b="\_$g(b,"Null")\_";", "Text")
    
    このコードの実行は、新しいアプリケーションを作成します.ログレコード

    改善
    ログ記録システムを作成しました.
  • まず最初に、オブジェクトの型引数を処理する可能性があります.
  • 第二に、格納された引数値からメソッドのコンテキストを復元する呼び出し.
  • オブジェクト型引数の処理
    ログに引数値を設定する行は、ArgumentSlistToStringメソッドで生成され、次のようになります.
    "\_$g(" \_ $lg($lg(List,i)) \_ ","\_$$$quote(..#Null)\_")\_"
    
    リファクタリングを行い、変数名とクラスを受け入れる別のgetArgumentValueメソッドに移動しますFormalSpecParsed ) 変数を行に変換するコードを出力します.既存のデータ型を使用して、オブジェクトをJSONに変換しますSerializeObject (ユーザコードからの呼び出しの場合)WriteJSONFromObject (オブジェクトをJSONに変換する場合)
    ClassMethod GetArgumentValue(Name As %String, ClassName As %Dictionary.CacheClassname) As %String
    {
        If $ClassMethod(ClassName, "%Extends", "%RegisteredObject") {
            // it's an object
            Return "_##class(App.Log).SerializeObject("_Name _ ")_"
        } Else {
            // it's a datatype
            Return "_$g(" _ Name _ ","_$$$quote(..#Null)_")_"
        }
    }
    
    ClassMethod SerializeObject(Object) As %String
    {
        Return:'$IsObject(Object) Object
        Return ..WriteJSONFromObject(Object)
    }
    
    ClassMethod WriteJSONFromObject(Object) As %String [ ProcedureBlock = 0 ]
    {
        Set OldIORedirected = ##class(%Device).ReDirectIO()
        Set OldMnemonic = ##class(%Device).GetMnemonicRoutine()
        Set OldIO = $io
        Try {
            Set Str=""
    
            //Redirect IO to the current routine - makes use of the labels defined below
            Use $io::("^"_$ZNAME)
    
            //Enable redirection
            Do ##class(%Device).ReDirectIO(1)
    
            Do ##class(%ZEN.Auxiliary.jsonProvider).%ObjectToJSON(Object)
        } Catch Ex {
            Set Str = ""
        }
    
        //Return to original redirection/mnemonic routine settings
        If (OldMnemonic '= "") {
            Use OldIO::("^"_OldMnemonic)
        } Else {
            Use OldIO
        }
        Do ##class(%Device).ReDirectIO(OldIORedirected)
    
        Quit Str
    
        // Labels that allow for IO redirection
        // Read Character - we don't care about reading
    rchr(c)      Quit
        // Read a string - we don't care about reading
    rstr(sz,to)  Quit
        // Write a character - call the output label
    wchr(s)      Do output($char(s))  Quit
        // Write a form feed - call the output label
    wff()        Do output($char(12))  Quit
        // Write a newline - call the output label
    wnl()        Do output($char(13,10))  Quit
        // Write a string - call the output label
    wstr(s)      Do output(s)  Quit
        // Write a tab - call the output label
    wtab(s)      Do output($char(9))  Quit
        // Output label - this is where you would handle what you actually want to do.
        // in our case, we want to write to Str
    output(s)    Set Str = Str_s Quit
    }
    
    オブジェクト型引数を持つログエントリは次のようになります.

    コンテキストの復元
    このメソッドのアイデアは、すべての引数を現在のコンテキストで利用できるようにすることです.このためには、ProperdureBlockメソッドパラメーターを使用できます.0に設定すると、このメソッド内で宣言されたすべての変数は、メソッドを終了するときに使用可能なままになります.我々のメソッドは、アプリケーションのオブジェクトを開きます.log classと引数のプロパティを逆シリアル化します.
    ClassMethod LoadContext(Id) As %Status [ ProcedureBlock = 0 ]
    {
        Return:'..%ExistsId(Id) $$$OK
        Set Obj = ..%OpenId(Id)
        Set Arguments = Obj.Arguments
        Set List = ..GetMethodArgumentsList(Obj.ClassName,Obj.MethodName)
        For i=1:1:$Length(Arguments,";")-1 {
            Set Argument = $Piece(Arguments,";",i)
            Set @$lg($lg(List,i)) = ..DeserializeObject($Piece(Argument,"=",2),$lg($lg(List,i),2))
        }
        Kill Obj,Arguments,Argument,i,Id,List
    }
    
    ClassMethod DeserializeObject(String, ClassName) As %String
    {
        If $ClassMethod(ClassName, "%Extends", "%RegisteredObject") {
            // it's an object
            Set st = ##class(%ZEN.Auxiliary.jsonProvider).%ConvertJSONToObject(String,,.obj)
            Return:$$$ISOK(st) obj
        }
        Return String
    }
    
    これが端末にどのように見えます.
    >zw
    >do ##class(App.Log).LoadContext(2)
    >zw
    
    a=1
    b=<OBJECT REFERENCE>[2@%ZEN.proxyObject]
    
    >zw b
    b=<OBJECT REFERENCE>[2@%ZEN.proxyObject]
    +----------------- general information ---------------
    |      oref value: 2
    |      class name: %ZEN.proxyObject
    | reference count: 2
    +----------------- attribute values ------------------
    |           %changed = 1
    |     %data("prop1") = 123
    |     %data("prop2") = "abc"
    |             %index = ""
    
    次は何ですか.
    キーの潜在的な改善は、メソッドの内部で作成された変数の任意のリストを持つLogクラスに別の引数を追加することです.
    結論
    マクロは非常にアプリケーション開発に便利です.
    質問
    コンパイル中に行番号を取得する方法はありますか?
    リンク
  • Part I. Macros
  • GitHub repository