2025/09/27

共通部品にチャレンジ:#8)LotusScript 実行ログ - 日付順ビューの作成

ログ出力ができるようになったので、ログを見やすく表示するビューが必要ですね。


使用するフィールド

ビューの作成で利用できるフィールドをまとめます。

項目 フィールド名 種類 補足 / 式
開始時刻 LogStartTime 日付/時刻  
DB 名 DBTitle テキスト  
タイトル Title テキスト 処理の名前、または、エージェント名
エラーフラグ IsError テキスト 1 = エラーあり
エラー LogError テキスト  最後のエラーメッセージ
実行サマリー LogSummary テキスト  最後のサマリーメッセージ
実行ユーザ名 UserName  名前  

エラーと実行サマリは出力関数をコールするたびに上書きしますので ”最後” に出力されたメッセージとなります。


ビューの作成

サンプルとして最初に掲載した画像のビューを作成します。年/月/日でカテゴライズして、日付順に表示します。

ビューの各列と設定をまとめました。列名に ( ) が付いているのは非表示列です。また、設定の ↓ は降順、▼ はカテゴリ列を表します。

列名 設定 補足 / 式
1 (背景色)   @If(
   IsError = "";
      (-1):(-1):(-1):(-1):(-1):(-1);
      255:255:240:(-1):(-1):(-1)
)
2 (年) @Year(LogStartTime)
3   ↓▼ @Text(@Year(LogStartTime)) + " 年"
4 (月) @Month(LogStartTime)
5   ↓▼ @Month(LogStartTime)
6 (日) @Day(LogStartTime)
7   ↓▼ @Text(@Day(LogStartTime)) + " 日"
8 開始 LogStartTime
9 DB名 DBTitle
10 タイトル Title
11 (背景色&文字色) @If(
   IsError = "";
      (-1):(-1):(-1):(-1):(-1):(-1);
      255:255:240:255:0:0
)
12 エラー / サマリ @If(
   IsError = "";
      LogSummary;
      LogError
)

エラーのログは目立つように背景を黄色、エラーメッセージを赤で表示します。列番号 1 と 11 でコントロールしています。この設定方法については『ビューで条件に応じて色を設定する方法』を参照してください。

今回はサンプルとして、ログを日付順に表示するビューを作成しました。実際に利用する際には、DB 別、ユーザ別、エラーのみのビューなどがあると便利です。必要に応じて作成しましょう。


次回予告

基本的な機能が完成したので、今後はログ出力であったらいいな的な機能を順に追加していきます。次回は、処理時間を測定する機能を追加します。スクリプト全体の処理時間だけでなく、ログ出力間の処理時間が閾値を越えると表示機能も作ります。


前回 共通部品にチャレンジ


2025/09/26

共通部品にチャレンジ:#7)LotusScript 実行ログ - 基本機能のテスト

前回までに作成したスクリプトライブラリの動作確認をします。簡単なサンプルプログラムを作成して、ライブラリの使い方と実行結果を確認しましょう。


エージェントの作成とテスト

新規でエージェントを作成します。名前には別名を付けた方がテストになりますね。トリガーはアクションメニューから実行として、対象は ”なし” とします。

サンプルで作成するプログラムは、1 ~ n までの総和を求めるプログラムです。計算の経過をログに出力します。

Option Declare
Use "StdLog"    '実行ログ の組み込み

Sub Initialize
   Dim i As Integer
   Dim iMax As Integer
   Dim iSum As Integer
   Dim s As String

   On Error GoTo ErrProc

   '初期化
   iMax = 10

   'ログの開始
   Call LogOpen("StdLog のテスト")
   Call PrintLog("1 ~ " & CStr(iMax) & " までの総和を求めます...")

   '1 ~ iMax までの総和を計算
   For i = 1 To iMax
      s = CStr(i) & ") " & CStr(iSum) & " + " & CStr(i) & " = "
      iSum = iSum + i
      '計算経過の出力
      Call PrintLog(s & CStr(iSum))
   Next

   Call PrintSummary("1 ~ " & CStr(iMax) & " までの総和は " & CStr(iSum) & " です。")

   'ログの終了
   Call LogClose()

   Exit Sub

ErrProc:
   Call PrintError(Error$ & " (Err = " & CStr(Err) & ", Erl = " & CStr(Erl) & ")")
   End
End Sub

ライブラリの使い方は、

  1. ライブラリ StdLog を Use で呼び出す
  2. LogOpen 関数でログ出力を開始
  3. PrintLog など Print* 関数でログを出力
  4. 最後に LogClose をコールしてログ出力を終了
となります。

実際に実行すると次のような文書が作成されます。


エラーログの出力のテスト

サンプルプログラムでは、不測の事態に備え On Error でエラー処理を記述しており、エラー発生時には PrintError 関数でそのエラー情報を出力しています。

その動作確認のため、For ループ内の最後の行にエラーを強制的に発生させます。

      Error 1000, "検証のために発生させたエラー"

実行結果を確認するとログ内では赤字でラーメッセージが表示され、エラーの箇所が一目瞭然ですね。

なお、エラーが発生した場合、End 命令でスクリプトを終了しています。LogClose をコールせずに終了しているのですが、『StdLog: LogClose() が実行されずに終了しました...』と出力されています。Terminate の設定が正しく動いていることが確認できます。


次回の予定

テスト実行できるようになり、ログがたまってくるとビューが欲しくなります。そこで、次回はログを見やすく表示するビューを作成します。


前回 共通部品にチャレンジ 次回


2025/09/25

共通部品にチャレンジ:#6)LotusScript 実行ログ - ログの終了(LogClose 関数)

#3 から開始したライブラリ作成作業の続きです。今回はログの終了処理について紹介します。これで基本機能が完成です。


◇ ログの終了(LogClose 関数)

ライブラリ利用者とのインターフェースとなる関数はシンプルです。終了処理を行うサブ関数 xLogClose をコールするだけです。

Public Function LogClose() As Boolean
   Call xLogClose()
End Function


◇ ログ終了の本体

ログが初期化され出力中であるかをはじめに確認します。その後、ログの終了メッセージを記録し、終了時間、通常ログとエラーの件数を記録して文書を保存します。

Function xLogClose() As Boolean
   If xbIsInit = False Then Exit Function

   '終了処理
   If xbIsOpen Then
      '処理終了メッセージの出力
      Call PrintLog(|ログ出力の終了|)
      xndLog.LogEndTime = Now      '処理時刻
      xndLog.LogMessageCount = xlMessage      '通常ログの件数
      xndLog.LogErrorCount = xlError      'エラーログの件数
      Call xndLog.Save(True, True)

      'フラグリセット
      xbIsOpen = False
      Set xndLog = Nothing

      xLogClose = True
   End If
End Function

ログ文書の保存後は、xbIsOpen を False に設定、文書を開放して Private 変数を整えます。これでログの終了は完了です。


◇ Terminate の定義

不測の事態に備えて、スクリプトライブラリの Terminate を記述します。ライブラリが閉じるときログが記録中だった場合、ログを強制的に終了させます。

Sub Terminate
   If xbIsOpen Then
      'Close できていない
      Call xPrintLog(True, xcsLibName & ": LogClose() が実行されずに終了しました...")
      Call xLogClose()
   End If
End Sub

これで LogClose() をコールし忘れた場合でも、ログが保存されます。また、Ctrl + Break で強制終了した場合にも対応できます。『LogClose() が実行されずに終了しました...』メッセージはエラーのフォントで表示させているので気づきやすくしています(エラー扱いではない)。


まとめ

このような共通部品では、安定性が重要となります。内部でエラーが起きないことはもちろんですが、想定外の使用に備えて、インターフェースとなる関数の最初でステータスのチェックを行っています。また、Terminate を活用して異常終了時でも、できる限りリカバリする点がポイントとなります。

これで、LotusScript 実行ログ出力の基本機能は完成です。次回は、このライブラリを利用して動作確認をします。


前回 共通部品にチャレンジ 次回


2025/09/24

共通部品にチャレンジ:#5)LotusScript 実行ログ - ログの出力(Print* 関数)

#3 から開始したライブラリ作成作業の続きです。今回はログを出力する関数 3 つを紹介します。


◇ 通常ログの出力(PrintLog 関数)

最初は通常のログを出力する PrintLog 関数です。xPrintLog 関数でログの出力を行っているのですが、この関数の役割は、エラーチェックと出力した通常ログのカウンタのインクリメントとなっています。

Public Function PrintLog(ByVal vsLogText As String) As Boolean
   If xbIsInit = False Then Exit Function
   If xbIsOpen = False Then Exit Function

   '通常ログとして、引数の文字列を出力
   If xPrintLog(False, vsLogText) Then
      PrintLog = True

      'カウンタインクリメント
      xlMessage = xlMessage + 1
    End If
End Function


◇ エラーログの出力(PrintError 関数)

通常ログと違い、エラーログの出力は、エラーメッセージを記録するフィールド LogError にエラーメッセージを記録して、エラーフラグ IsError に "1" をセットしています。

Public Function PrintError(ByVal vsErrorMessage As String) As Boolean
   If xbIsInit = False Then Exit Function
   If xbIsOpen = False Then Exit Function

   'エラーログとして、引数の文字列を出力
   If xPrintLog(True, vsErrorMessage) Then
      'エラーメッセージを記録
      xndLog.LogError = vsErrorMessage
      'エラーフラグを記録
      xndLog.IsError = "1"

      PrintError = True

      'カウンタインクリメント
      xlError = xlError + 1
   End If
End Function


◇ サマリーの出力(PrintSummary)

「8 件の文書を処理しました。」というような処理結果を出力する機能がサマリーです。エラーと同様に専用のフィールド LogSummary にメッセージを記録します。それ以外の処理は通常ログと同じです。

Public Function PrintSummary(ByVal vsLogSummary As String) As Boolean
   If xbIsInit = False Then Exit Function
   If xbIsOpen = False Then Exit Function

   '通常ログとして、引数の文字列を出力
   If xPrintLog(False, vsLogSummary) Then
      'サマリを記録
      xndLog.LogSummary = vsLogSummary

      vsLogSummary = True

      'カウンタインクリメント
      xlMessage = xlMessage + 1
   End If
End Function


◇ ログ出力の本体

この関数がログメッセージを出力する本体で、上記 3 つの関数からコールされています。引数 vbIsError が True の場合はエラー、False の場合は通常ログを表します。

Private Function xPrintLog(ByVal vbIsError As Boolean, ByVal vsMessage As String) As Boolean
   '通常ログのスタイル適用
   Call xnrti.AppendParagraphStyle(xnrtpsMsg)

   '日時
   Call xnrti.AppendStyle(xnrtsDT)
   Call xnrti.AppendText(Format(Now, "yyyy/mm/dd hh:nn:ss"))

   'ログ(メッセージ)
   If vbIsError Then
      '通常ログ
      Call xnrti.AppendStyle(xnrtsErr)
   Else
      'エラーログ
      Call xnrti.AppendStyle(xnrtsMsg)
   End If
   Call xnrti.AddTab(1)    '日付とログの間
   Call xnrti.AppendText(vsMessage)

   xPrintLog = True
End Function

処理の流れは、段落スタイルをセットして、日付とログメッセージを出力しています。文字色を変えるため出力前に AppendStyle であらかじめ定義したフォントを指定しています。メッセージの出力ではエラーかどうかに応じてフォントを切り替えています。


前回 共通部品にチャレンジ 次回


2025/09/23

共通部品にチャレンジ:#4)LotusScript 実行ログ - ログの開始(LogOpen 関数)

前回から開始したスクリプトライブラリ作成作業の続きです。今回はログ出力を開始する処理に関して記載します。


◇ ログの開始(LogOpen 関数)

まず、ライブラリ利用者とのインターフェースとなる LogOpen 関数です。初期化できていない場合とすでに Open 済みの場合はキャンセルするエラーチェックをしてから、処理を行います。

ログ DB に新規文書を作成し、ログ文書の初期化(後述)を行い、フラグや戻り値を整えています。

Public Function LogOpen(ByVal vsLogTitle As String) As Boolean
   Dim s As String

   On Error GoTo Err_General

   If xbIsInit = False Then Exit Function
   If xbIsOpen = True Then Exit Function

   'ログ文書の作成
   Set xndLog = xndbLog.CreateDocument()
   xndLog.Form = "fStdLog"

   'ログ文書の初期化
   Call xInitLog(vsLogTitle)

   '終了処理
   xbIsOpen = True
   LogOpen = True

   '処理開始メッセージの出力
   Call PrintLog(xcsLibName & |: ログ出力の開始 (| & xcsLibName & | | & xcsLibVersion & |)|)

   Exit Function

Err_General:
   s = "LogOpen でエラーが発生しました。" & Chr(10)
   s = "問題を解決するまでログ出力はできません。" & Chr(10)
   s = s & Error$ & " (Err = " & CStr(Err) & ", Erl = " & CStr(Erl) & ")"
   MsgBox s, 16, xcsLibName
   Exit Function
End Function

最後に PrintLog をコールして、ログの開始とライブラリのバージョンを記録します。この関数はログを出力する関数なのですが、これについては次回紹介します。


◇ ログ文書の初期化

xInitLog はログの基本情報を文書にセットする関数です。ログの開始時刻、エージェントの情報、ユーザ名や DB の情報を記録しています。

Function xInitLog(ByVal vsLogTitle As String)
   Dim na As NotesAgent
   Dim sTitle As String
   Dim v As Variant
   Dim sName As String

   '初期化
   sTitle = vsLogTitle
   sName = xns.UserName

   '開始日時
   xndLog.LogStartTime = Now

   'エージェント情報
   Set na = xns.CurrentAgent
   If Not(na Is Nothing) Then
      'エージェントと別名を分離
      v = Split(na.Name, "|")
      xndLog.AgentName = xRemoveFirst(v)    'エージェント名
      xndLog.AgentAlias = v                                     'エイリアス名

      'タイトルの指定がない場合、エージェント名をセット
      If sTitle = "" Then
         sTitle = xndLog.AgentName(0)
      End If
   End If

   'ログタイトル
   xndLog.Title = sTitle

   'ユーザ名
   xndLog.UserName = sName

   'サーバエージェント
   If xns.IsOnServer Then
      xndLog.IsOnServer = "1"
   End If

   'DB情報
   xndLog.DBTitle = xndbCur.Title
   xndLog.Server = xndbCur.Server
   xndLog.FilePath = xndbCur.FilePath

   'リッチテキスト準備
   Set xnrti = xndLog.CreateRichTextItem("Body")

   'フォントの準備
   Call xInitFont(xnrti)
End Function

また、リッチテキストフィールドを作成して、ライブラリ内の Private 変数に格納しています。このフィールドは、さまざまなシーンで利用するのでライブラリ全体からアクセスできるようにしています。


◇ エージェント名と別名を分離

NotesAgent クラスの Name プロパティは別名を含めて "|" 区切りで返します。そこで Split 関数で配列化した後、xRemoveFirst 関数でエージェント名と別名を分離しています(前述の xInitLog  関数の下記部分)。

Private Function xInitLog(ByVal vsLogTitle As String)
   Dim na As NotesAgent
         ・・・
   'エージェント情報
   Set na = xns.CurrentAgent
   If Not(na Is Nothing) Then
      'エージェントと別名を分離
      v = Split(na.Name, "|")
      xndLog.AgentName = xRemoveFirst(v)    'エージェント名
      xndLog.AgentAlias = v                                     'エイリアス名
         ・・・
 End Function

xRemoveFirst 関数は、引数の配列から最初の要素を削除(引数の配列を更新)し、削除した要素を戻り値として返します。

Private Function xRemoveFirst(rvArray As Variant) As Variant
   Dim i As Integer

   '最初の要素を戻り値にセット
   i = LBound(rvArray)
   xRemoveFirst = rvArray(i)

   '各要素を1つ前に詰める
   For i = LBound(rvArray)+1 To UBound(rvArray)
      rvArray(i-1) = rvArray(i)
   Next

   '最大要素番号を1つ減らす
   If LBound(rvArray) = UBound(rvArray) Then
      '要素が一つなので初期化
      ReDim rvArray(UBound(rvArray))
   Else
      ReDim Preserve rvArray(UBound(rvArray)-1)
   End If
End Function


◇ フォントの準備

ログ文書の初期化の最後でコールしている xInitFont 関数はログ出力で利用するフォント情報を初期化する関数です。今回のログ出力では、項目ごとにフォントを切り替える仕様としています。頻繁にアクセスするので、フォント情報もライブラリ内の Private 変数に格納します。

Private Function xInitFont(vnrti As NotesRichTextItem)
   Dim sFont As String

   sFont = "メイリオ"

   '通常のメッセージ
   Set xnrtsMsg = xns.CreateRichTextStyle()
   xnrtsMsg.NotesColor = 0     '黒
   xnrtsMsg.FontSize = 10
   xnrtsMsg.NotesFont = vnrti.GetNotesFont(sFont, True)
   xnrtsMsg.Bold = False

   'ログ時刻
   Set xnrtsDT = xns.CreateRichTextStyle()
   xnrtsDT.NotesColor = 15    '薄いグレー
   xnrtsDT.FontSize = 10
   xnrtsDT.NotesFont = vnrti.GetNotesFont(sFont, True)
   xnrtsDT.Bold = False

   'エラーメッセージ
   Set xnrtsErr = xns.CreateRichTextStyle()
   xnrtsErr.NotesColor = 2     '赤
   xnrtsErr.FontSize = 10
   xnrtsErr.NotesFont = vnrti.GetNotesFont(sFont, True)
   xnrtsErr.Bold = True    ’太字
End Function

通常のメッセージは黒、エラーは太字の赤字として目立たせています。また、ログの出力時刻はさほど重要ではないのでグレーとしています。

なお、フォントをセットする(NotesFont プロパティ)ためにリッチテキストフィールド(NotesRichTextItem オブジェクト)が必要です。文書を作成してからでないと利用できないため LogOpen 関数コール時に初期化しています。


前回 共通部品にチャレンジ 次回


2025/09/22

共通部品にチャレンジ:#3)LotusScript 実行ログ - ライブラリの使い方と作成

LotusScript 実行ログ出力機能作成の 3 回目です。いよいよスクリプトライブラリの作成を始めます。ライブラリの使い方を整理してから、ライブラリのコーディングを始めます。


ライブラリの使い方

LotusScript 実行ログを出力する機能は、スクリプトライブラリにまとめます。アプリの開発者は、自身が作成するエージェントやフォームのボタンなど、アプリケーション内でこのライブラリを利用して、処理状況や実行結果をログ DB に残します。開発者は、ログ管理に関しては部品を利用するだけとなるため開発効率が上がるというのが狙いです。

ライブラリのインターフェースとなる関数と機能は以下の通りとなります。

LogOpen ログの記録を開始
PrintLog ログ(通常のメッセージ)を記録
PrintError エラーログを記録(赤字で目立つように記録)
PrintSummary 処理結果のサマリを記録
LogClose ログの記録を終了し、ログ文書を保存

LogOpen でログの記録を開始、Print* でログの中身を出力、LogClose でログ DB に文書として保存して終了します。NotesLog クラスとメソッド名は違いますが、使い方の流れは同じですね。


ライブラリの作成と初期化

新規で LotusScript のライブラリを作成します。

名称 StdLog

まずは、ライブラリの初期化に関連する部分を紹介します。


◇ ライブラリの定義

ライブラリの定義にあたる (Options)、(Declarations) は次の通り記述します。

Option Declare

Private xns As NotesSession
Private xndbCur As NotesDatabase
Private xndbLog As NotesDatabase
Private xndLog As NotesDocument
Private xnrti As NotesRichTextItem

'ライブラリ情報
Private Const xcsLibName = "StdLog"
Private Const xcsLibVersion = "1.0.0"
Private Const xcsFilePath = "LS_Log.nsf"    'ログDBのパス

'段落スタイル
Private xnrtpsMsg As NotesRichTextParagraphStyle     '通常ログ

'フォント定義
Private xnrtsDT As NotesRichTextStyle        'ログ時刻
Private xnrtsMsg As NotesRichTextStyle     '通常のメッセージ
Private xnrtsErr As NotesRichTextStyle       'エラーメッセージ

'ステータス管理
Private xbIsInit As Boolean         '初期化したか?
Private xbIsOpen As Boolean     'ログDBが取得済みか?

'カウンタ
Private xlMessage As Long    '通常ログ出力件数
Private xlError As Long           'エラーログ出力件数


◇ ライブラリの初期化

続いては、ライブラリの初期化です。ログ DB の取得を行い、取得できたら初期化できたことを xbIsInit に設定します。

Sub Initialize
   'ログDB取得
   Set xns = New NotesSession
   Set xndbCur = xns.CurrentDatabase
   Set xndbLog = xns.GetDatabase(xndbCur.Server, xcsFilePath, False)

   '取得成功なら初期化は OK
   If Not (xndbLog Is Nothing) Then
      xbIsInit = True
   End If

   '段落スタイル
   Call xInitParagraphStyle()
End Sub


◇ 段落スタイルの準備

初期化関数からコールしている xInitParagraphStyle は段落スタイルを設定準備する関数です。ログを出力する際に都度利用するので、あらかじめ準備しておき、いつでも利用できるようにします。

Private Function xInitParagraphStyle()
   '通常のメッセージ
   Set xnrtpsMsg = xns.CreateRichTextParagraphStyle()
   xnrtpsMsg.FirstLineLeftMargin = 1 * RULER_ONE_INCH
   xnrtpsMsg.LeftMargin = 6.5 * RULER_ONE_CENTIMETER
End Function

この設定を利用した段落は、1 行目の左マージンが 1 インチ(2.54cm)、2 行目以降が 6.5cm となります。これで、ログメッセージが長く右端で折り返しても日時の下には表示されません。


次回の予定

今回はライブラリの作成と初期化について紹介しました。次回以降はインターフェースとなる関数を順に紹介します。


前回 共通部品にチャレンジ 次回


2025/09/21

共通部品にチャレンジ:#2)LotusScript 実行ログ - フォームの作成

まずはログを表示するフォームを作成します。フォームのイメージは次の通りです。

現時点ではログ出力機能として最低限必要な項目のみ配置しています。その他項目は、関連機能追加のタイミングで追加することとします。なお、フォーム上の の部分は非表示の項目です。

作成に必要な情報を順に記載します。


フォーム情報

新規 DB を作成して、次の新規フォームを作成します。

フォーム名 1.LotusScript実行ログ
別名 fStdLog
ウィンドウタイトル @Text(LogStartTime; "D1T1S3") + " - " + Title

作成するフィールドは下記の通りです。このフォームは開発者やアプリの運用担当だけが見る前提にしていますので、理由がない限り ”編集可能” としています。


◇ 管理者([Admin] ロール)セクション

フォームにセクションを作成して、その中に管理用のフィールドを作成します。

項目 フィールド名 種類 補足
エラーフラグ IsError テキスト 編集可能 1 = エラーあり
通常ログ件数 LogMessageCount 数値 編集可能
エラー件数 LogErrorCount 数値 編集可能

セクションは [Admin] ロールを持っている人のみ表示させるよう非表示式を指定します。

!(
   @IsMember("[Admin]"; @UserRoles)
)

なお、セクションは初期状態を閉じておきましょう。これだけでロールを持たないユーザには、セクション丸ごと非表示にできます。


◇ ヘッダ情報

ログのタイトルや実行時間などの基本的な情報を表示するエリアです。次のフィールドを配置します。

項目 フィールド名 種類 補足 / 式
  Title テキスト 編集可能  
実行時間 LogStartTime 日付/時刻 編集可能  
LogEndTime 日付/時刻 編集可能  
エージェント AgentName テキスト 編集可能
AgentAlias テキスト 編集可能  複数値も可
IsOnServer チェックボックス 編集可能  
エラー LogError テキスト 編集可能  
LogErrorCount_D 数値 表示用の計算結果  LogErrorCount - 1
実行サマリー LogSummary テキスト 編集可能  

なお、エージェントの項目は、LotusScript がエージェントから実行された場合のみ出力されるように作成していきます。よって、AgentName に値があるときだけ表示させます。

!(AgentName != "")

エラーについては、エラーがあった場合だけの表示とします。

!(IsError != "")

ただし、エラー件数は、2 件以上の場合のみ表示とします。

!(LogErrorCount >= 2)


◇ 実行環境の情報

スクリプトを実行する DB とユーザの情報、ACL、実行環境を表示するエリアです。ACL と実行環境については、追って作成します。それに備えて、[基本情報]タブだけが存在するタブ形式の表にフィールドを配置します。

項目 フィールド名 種類 補足
DB 情報 DBTitle テキスト 編集可能  
Server テキスト 編集可能  
FilePath テキスト 編集可能  
 実行ユーザ名 UserName テキスト 編集可能  


◇ 実行ログ

フォームの下部には LotusScript 実行ログを記録するリッチテキストフィールドを配置します。

項目 フィールド名 種類 補足
 実行ログ Body リッチテキスト 編集可能  


前回 共通部品にチャレンジ 次回


2025/09/20

共通部品にチャレンジ:#1)LotusScript 実行ログ - 動作記録を一元管理

久しぶりに新しい連載を始めます。

とはいってもまったく新しいことを始めるのではなく、連載『作ってみよう』の派生です。『作ってみよう』は、アプリケーションを作ることを目的にしていますが、この連載では、アプリケーションで利用できる共通部品の作成に特化します。

最初のネタは、『LotusScript の実行ログ』の収集です。アプリ数が増えてくると各アプリが正常に動作しているか、エラーは発生していないか、日々巡回するのは手間がかかります。LotusScript の実行ログが一元管理されているだけでもアプリの運用監視が楽になりますね。

先日『LotusScript の実行確認と NotesLog クラス』で NotesLog クラスを使ったログの取得方法を紹介しました。このクラスを使用して、ログを指定したデータベースに出力できます。ただ、出力される項目は、時間、エージェント名、ユーザ名とログだけでかなりシンプルです。

これで一元管理はできるのですが、もう少し得られる情報が充実しているほうが、何かあったときの対応が楽になります。そこで、今回は自作のログ出力機能を作成したいと思います。


LotusScript 実行ログ

出力されるログのイメージはこんな感じです。上部に実行時間やエージェント名、エラーメッセージなどのヘッダ情報、中段にはユーザやアクセス権などの実行環境の情報、下部のリッチテキストにログを出力しています。

ログ部分は大量に出力されることを前提にリッチテキストにしています。せっかくのリッチテキストなので、エラーは赤の太字、日時は薄く表示してメリハリをつけ、見やすくしています。


◇ 経過時間の表示

また、ログ出力間の時間を測定し、閾値を越えると経過時間を表示する機能も作ります。簡易的にではありますが、レスポンスの遅い部分が調査できます。


◇ ACL 情報

LotusScript の実行者のアクセス権の情報を表示します。ACL の権限レベルだけでなく、ロールや文書の削除権限などの ACL のオプション情報も収集します。エラー発生時など権限の影響判定がしやすくなりますね。


◇ 実行環境

[実行環境]タブには、実行したクライアントのバージョンやプラットフォームの情報を表示します。環境に依存する問題の調査に利用できます。


作り方は次回以降

作成する設計要素は、ログを出力する DB のフォームとビュー、そして、ログを出力するためのスクリプトライブラリとなります。実アプリでログ機能を組み込む際には、スクリプトライブラリをアプリ内にコピペして、それを Use して利用するという流れとなります。

次回から順に機能の作成を進めます。


共通部品にチャレンジ 次回


2025/09/13

配列の次元数を取得する方法

今回は超個人的な備忘録です。

通常の業務アプリで必要となるシーンは思い浮かばないのですが、先日、汎用的なツールを作成しているときに必要となった処理です。それは、Variant 型変数に配列が入っていた時にその配列の次元数を確認する方法です。


配列であるかの確認

Variant 型 に配列が入っているかは、IsArray 関数で判定できます。

If IsArray(vUnknown) = True Then
   'vUnknown は配列
Else
   'vUnknown は配列ではない
End If


要素数の確認と次元

上記関数では、配列かどうかの判定はできますが、その次元数は判定できません。LotusScript にそれを取得する関数がないようなので、自作することにしました。

利用したのは UBound(LBound でも可)関数です。配列の要素数の最大値(最小値)を取得する関数なのですが、2 つ目の引数に次元を指定できます。

UBound ( arrayName [ , dimension ] )

例えば、3 次元配列の場合、以下のように記述すれば各次元の要素数の最大値が取得できます。

Dim aiArray(5, 3, 15) As Integer
Dim iMax1 As Integer
Dim iMax2 As Integer
Dim iMax3 As Integer
iMax1    = UBound(aiArray, 1)     ' 5
iMax2    = UBound(aiArray, 2)     ' 3
iMax3    = UBound(aiArray, 3)     ' 15


次元数の確認

UBound で存在しない次元数を指定した場合、エラーが発生します。これを利用して Variant 変数内の次元数を確認します。

次元数を返す関数のサンプルは次の通りです。

Function GetDimension(vaArray As Variant) As Integer
   Dim i As Integer
   Dim j As Integer

   On Error GoTo GeneralError

   For i = 1 To 8
      ' 存在しない次元を指定するとここでエラーになる
      j = UBound(vaArray, i)

      'エラーがないのその次元は存在
      GetDimension = i
   Next

ExitFunc:
   Exit Function

GeneralError:
   Resume ExitFunc
End Function


もし引数に配列でない値がセットされた場合には、初回の UBound でエラーが発生するので、0 が返されます。


2025/09/12

QueryAccessPrivileges の判定と演算子の活用

前回 の記事の中で ACL のオプションを取得する QueryAccessPrivileges メソッドを紹介しました。このメソッドの戻り値は、以下の値の組み合わせだと紹介しました。例えば、文書の作成ができて、削除のできる場合には、1 + 2 の 3 となります。これにパブリック文書の読者権限も持っていると 67 となります。

上記のように、戻り値で組み合わされた値が返ってきたとき、あなたならどのように判定しますか?

定数
1 DBACL_CREATE_DOCUMENTS
2 DBACL_DELETE_DOCUMENTS
4 DBACL_CREATE_PRIV_AGENTS
8 DBACL_CREATE_PRIV_FOLDERS_VIEWS
16 DBACL_CREATE_SHARED_FOLDERS_VIEWS
32 DBACL_CREATE_SCRIPT_AGENTS
64 DBACL_READ_PUBLIC_DOCUMENTS
128 DBACL_WRITE_PUBLIC_DOCUMENTS
256 DBACL_REPLICATE_COPY_DOCUMENTS


オプションの値の意味

話を単純化するために上から 4 つの場合を考えます。オプションの値は 1、2、4、8 で、取りうる値(組み合わせ)は 0 から 15 となります。列にオプションの値、行に取りうる値を整理すると次のようになります。

1 2 4 8
0 × × × ×
1 × × ×
2 × × ×
3 × ×
4 × × ×
5 × ×
6 × ×
7 ×
8 × × ×
9 × ×
10 × ×
11 ×
12 ××
13 ×
14 ×
15

これですぐわかりますね。オプションの値は 2 進数の各ビットの値を表すということになります。


判定方法

では、13 を例にどのビットが立っているのか考えてみます。大きいビットから考えて、

  1. 8 で割って 1 以上(〇)
  2. 8 で割った余り 5 を 4 で割って 1 以上(〇)
  3. 4 で割った余り 1 を 2 で割って 1 未満(×)
  4. 2 で割った余りが 1(〇)

というよな判定となります。

これを逐一 If 文で書くと結構な分量になります。また、QueryAccessPrivileges のように 256( = 9 ビット)までとなるととんでもない分量になることが想像できます。

そこで、プログラミングらしく、ループを使って効率的に判定する方法を考えてみましょう。

Sub Initialize
   Dim iBit As Integer
   Dim iSrc As Integer    'サンプル地
   Dim iOpt As Integer   'オプションの値

   iSrc = 13

   '上のビットから順に処理
   For iBit = 3 To 0 Step -1
      'ビットの値を算出
      iOpt = 2 ^ iBit

      'ビットが立っているか判定
      If iSrc / iOpt >= 1 Then
         MsgBox CStr(iOpt) & " = 〇"
      Else
         MsgBox CStr(iOpt) & " = ×"
      End If

      'ビットの値の余りを算出
      iSrc = iSrc Mod iOpt
   Next
End Sub

べき乗の演算子(^)と剰余を求める命令(mod)を使い、上のビットから演算を進めることで、ループを効率的に活用しています。

なお、If 文の判定では、判定方法に記載した通り割り算を使用しましたが、以下のように書き換えることができます。

      If iSrc >= iOpt Then

割り算した結果を比較して判定するより、整数値を比較するだけの方が明らかに演算コストが安いですよね。


論理演算の動作

もっと簡単に判別する方法はないでしょうか? 実はあるんです。

それは、論理演算子 And を使用する方法です。If 文で複数の条件を記述する際に利用するあの And のことです。

      If a = b And b = c Then

And 演算子は上記のような使い方のほかに 2 つの値のビットを比較し、その結果を返す機能があります(上記判定もその一部)。事例に挙げた 13 の場合と 3 の And の結果は次のようになります。

   13 And 8 = 1101 And 1000 = 1000 = 8
   3 And 8 = 0011 And 1000 = 0000 = 0

And 一つあればそのビットが立っているか判定できることがわかります。


論理演算の活用

最初の表の定数は 1, 2, 4, 8, 16 … と各ビットの値が定数化されています。この定数と And とを活用すると保持しているオプションが ”ワンパン” で判定できます。

例えば『個人フォルダ/ビューの作成』を持っているか判定する場合、次のように記述します(iOpt に QueryAccessPrivileges の戻り値が入っている前提)。

   If (iOpt And DBACL_CREATE_PRIV_FOLDERS_VIEWS) > 0 Then


まとめ

今回は、QueryAccessPrivileges の戻り値を題材に論理演算子の活用方法についてまとめました。

ご存じの通り、コンピュータは 2 進数で動作しています。それを活用した演算子や言語仕様が各所にちりばめられています。10 進数で姓つしている私たちには気が付きにくいのですが、うまく活用することで、超お得になることがあります。

デザイナーヘルプやサンプルを見て、2 進数っぽいものが出てきたら調べてみましょう。お宝が発見できるかもしれませんよ。