言った・言わないを防ぐ!Power Appsで誰がいつ入力・変更したかログを残す設計

データがおかしくなったとき、誰がいつ変えたのかがわからないと原因調査が止まります。Power Appsにログ保存の仕組みを組み込んでおくだけで、そのストレスがなくなります。

ログを残さないと何が起きるか

現場でよくある話です。SharePointリストを使った申請アプリを運用していたら、ある日突然登録内容がおかしいと報告が来ました。担当者に確認しても自分は入力していない、自分が変えたわけじゃないと言う。SharePoint標準のバージョン履歴を確認すれば変更者はわかりますが、Power Appsから直接Patchした場合、SharePoint側に誰が何の項目を変えたかの詳細は残りません。

私が社内で最初にアプリを作ったときも、ログ機能を後付けしてかなり苦労しました。最初から設計に組み込んでおけばよかったと今でも思っています。特に承認フローや在庫管理のような重要なデータを扱うアプリほど、ログは必須です。

ログがあれば、問題発生時、2月14日 15:23に田中さんが数量を5から12に変更したという事実を即座に確認できます。言った・言わないの水掛け論がなくなり、何より担当者への不当な疑いを防ぐことができます。

ログ用リストの設計

まず、本番データとは別にログ専用のSharePointリストを用意します。本番リストにログ列を追加する方法もありますが、データが混在して見づらくなるため、リストを分けることをおすすめします。

ログリストの列構成

最低限必要な列は以下の5つです。

列名内容
Titleテキスト操作種別(登録・更新・削除など)
OperationByテキスト操作したユーザーのメールアドレス
OperationAt日付と時刻操作日時
TargetID数値操作対象レコードのID
Detail複数行テキスト変更内容の詳細(任意)

最初はシンプルな5列で十分です。運用しながら必要な情報が見えてきたら列を追加していくのが現実的です。

Power AppsからログをPatchする実装

ログの書き込みはPatch関数1本で完結します。本番データの保存処理と同じタイミングで、ログリストにも書き込むように設定します。

新規登録時のログ保存

申請データを新規作成するボタンのOnSelectに、本番データへのPatchとログへのPatchを並べて書きます。

// 本番データを保存
Patch(
  '申請データ',
  Defaults('申請データ'),
  {
    Title: TextInput_Subject.Text,
    担当者: TextInput_Name.Text,
    金額: Value(TextInput_Amount.Text)
  }
);

// ログを記録
Patch(
  '操作ログ',
  Defaults('操作ログ'),
  {
    Title: "新規登録",
    OperationBy: User().Email,
    OperationAt: Now(),
    TargetID: Last(Sort('申請データ', ID, SortOrder.Descending)).ID,
    Detail: "件名:" & TextInput_Subject.Text & " / 担当者:" & TextInput_Name.Text
  }
)

2つのPatch関数をセミコロンでつなぐだけです。Power Appsのセミコロン区切りは順番に実行されます。本番データを先に保存し、そのIDをログに記録する流れになります。

Patch関数の基本的な書き方についてはPatch関数の使い方の記事で詳しく解説しています。Defaults関数の使い方や列の型ごとの値指定も確認できるので、あわせて参照してください。

更新時のログ保存

既存レコードを更新する場合は、変更前後の値を Detail に記録しておくと後から見やすくなります。変更前の値はギャラリーで選択したレコード(Gallery.Selected)から取得できます。

// 本番データを更新
Patch(
  '申請データ',
  Gallery_Main.Selected,
  {
    金額: Value(TextInput_Amount.Text)
  }
);

// 変更前後をログに記録
Patch(
  '操作ログ',
  Defaults('操作ログ'),
  {
    Title: "更新",
    OperationBy: User().Email,
    OperationAt: Now(),
    TargetID: Gallery_Main.Selected.ID,
    Detail: "金額変更:" & Text(Gallery_Main.Selected.金額) & " → " & TextInput_Amount.Text
  }
)

変更前の値を Gallery_Main.Selected.金額 で、変更後の値を TextInput_Amount.Text で取得し、Detailに「変更前 → 変更後」の形で文字列結合しています。この形式にしておくと、ログを見ただけで何がどう変わったか一目でわかります。

レコードの更新パターンについてはPatch関数で既存レコードを更新する方法の記事も参考になります。Gallery.SelectedとvarRecordの使い分けも解説しています。

ログをアプリ内で確認できるようにする

ログリストはSharePointで直接確認することもできますが、アプリ内にログ表示画面を作ると現場での使い勝手が上がります。ギャラリーを使った実装はシンプルです。

対象レコードのログだけ絞り込む

ギャラリーの Items に Filter 関数を使って、選択中のレコードに関するログだけ表示します。

Filter(
  '操作ログ',
  TargetID = Gallery_Main.Selected.ID
)

さらに日時の新しい順に並べるなら Sort を追加します。

SortByColumns(
  Filter('操作ログ', TargetID = Gallery_Main.Selected.ID),
  "OperationAt",
  SortOrder.Descending
)

ギャラリーのテンプレート内に OperationAt(日時)・OperationBy(操作者)・Detail(詳細)のラベルを並べれば、最新の操作履歴が時系列で確認できる画面が完成します。

実装時に気をつけること

ログ実装でよくある失敗が、Patchの順序によるIDの取得ミスです。新規登録の直後に最後に追加されたレコードのIDを取るため、Last(Sort('申請データ', ID, SortOrder.Descending)).ID を使う方法が簡便ですが、複数ユーザーが同時に使うアプリでは別の人が登録したIDを取ってしまうリスクがあります。

より確実な方法は、Patchの戻り値を変数で受け取ることです。Patch関数は保存したレコードを戻り値として返すので、以下のように書けば取得したIDが正確です。

Set(
  varNewRecord,
  Patch(
    '申請データ',
    Defaults('申請データ'),
    {
      Title: TextInput_Subject.Text
    }
  )
);

Patch(
  '操作ログ',
  Defaults('操作ログ'),
  {
    Title: "新規登録",
    OperationBy: User().Email,
    OperationAt: Now(),
    TargetID: varNewRecord.ID
  }
)

Set(varNewRecord, Patch(...)) の形で、Patchの戻り値をそのまま変数に格納します。次の行で varNewRecord.ID を参照すれば、今作ったレコードのIDが確実に取れます。SharePointへのデータ読み書きの基本についてはSharePointリストのCRUDパターンの記事も参考になります。

もうひとつ注意したいのが、ログが膨らみすぎる問題です。アクティブに使われるアプリでは、1日に数百件のログが積み上がることもあります。SharePointリストは委任制限(標準2000件)があるため、古いログを定期的にアーカイブするPower Automateのフローを組み合わせることをおすすめします。

まとめ

ログ機能の実装はPatch関数を1本追加するだけです。難しいことは何もありませんが、後付けになると既存のボタンをすべて修正する手間が発生します。新しいアプリを作るときには最初の設計に組み込む習慣をつけると、後々の運用がぐっと楽になります。

入力ミス防止の全体的な設計についてはPower Apps入力ミス防止ガイドにまとめています。バリデーション・UX改善・ログ保存を組み合わせた設計で、信頼性の高いアプリを現場に届けましょう。積み上げた仕組みが業務を守ってくれる、そんな実感が、市民開発を続ける原動力になっていきます。

Xでフォローしよう