diff --git a/Editor/Localization/ja.po b/Editor/Localization/ja.po
index dbcd845..e39a2b0 100644
--- a/Editor/Localization/ja.po
+++ b/Editor/Localization/ja.po
@@ -87,13 +87,21 @@ msgstr "JUnitレポート出力パス"
msgid "JUnit report output path"
msgstr "JUnit形式のレポートファイル出力パス(省略時は出力されない)"
+# logger
+msgid "Logger"
+msgstr "ロガー"
+
+# logger tooltip
+msgid "Logger used for this autopilot settings. If omitted, Debug.unityLogger will be used as default."
+msgstr "オートパイロットが使用するロガー指定します。省略時は Debug.unityLogger がデフォルトとして使用されます"
+
# reporter
msgid "Reporter"
msgstr "レポータ"
# reporter tooltip
msgid "Reporter that called when some errors occurred in target application"
-msgstr "対象のアプリケーションで発生したエラーを通知するレポータ"
+msgstr "対象のアプリケーションで発生したエラーを通知するレポータを指定します"
# obsolete slack settings
msgid "Slack settings will be moved to SlackReporter"
@@ -429,3 +437,55 @@ msgstr "Automated QAパッケージのRecorded Playbackウィンドウで記録
# composite reporters
msgid "Reporters"
msgstr "レポータ"
+
+
+#: Editor/UI/Loggers/ 共通
+
+# description (same as AutopilotSettingsEditor.cs)
+msgid "Description"
+msgstr "説明"
+
+# description tooltip
+msgid "Description about this logger instance"
+msgstr "このLoggerインスタンスの説明"
+
+
+#: Editor/UI/Loggers/CompositeLoggerEditor.cs
+
+# Loggers
+msgid "Loggers"
+msgstr "Loggers"
+
+# Loggers tooltip
+msgid "Loggers to delegates"
+msgstr "出力を委譲するLoggerを指定します"
+
+
+#: Editor/UI/Loggers/ConsoleLoggerEditor.cs
+
+# Filter LogType
+msgid "Filter LogType"
+msgstr "フィルタリングLogType"
+
+# Filter LogType tooltip
+msgid "To selective enable debug log message"
+msgstr "選択したLogType以上のログ出力のみを有効にします"
+
+
+#: Editor/UI/Loggers/FileLoggerEditor.cs
+
+# Output Path
+msgid "Output File Path"
+msgstr "出力ファイルパス"
+
+# Output Path tooltip
+msgid "Log output file path. Specify relative path from project root or absolute path."
+msgstr "ログ出力ファイルのパス。プロジェクトルートからの相対パスまたは絶対パスを指定します"
+
+# Timestamp
+msgid "Timestamp"
+msgstr "タイムスタンプを追加"
+
+# Timestamp tooltip
+msgid "Output timestamp to log entities"
+msgstr "ログエンティティにタイムスタンプを出力します"
diff --git a/Editor/UI/Loggers.meta b/Editor/UI/Loggers.meta
new file mode 100644
index 0000000..0691f8e
--- /dev/null
+++ b/Editor/UI/Loggers.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 88a22ed087bb4e048f747deb0e18a426
+timeCreated: 1714865499
\ No newline at end of file
diff --git a/Editor/UI/Loggers/CompositeLoggerEditor.cs b/Editor/UI/Loggers/CompositeLoggerEditor.cs
new file mode 100644
index 0000000..3550ec9
--- /dev/null
+++ b/Editor/UI/Loggers/CompositeLoggerEditor.cs
@@ -0,0 +1,35 @@
+// Copyright (c) 2023-2024 DeNA Co., Ltd.
+// This software is released under the MIT License.
+
+using DeNA.Anjin.Loggers;
+using UnityEditor;
+using UnityEngine;
+
+namespace DeNA.Anjin.Editor.UI.Loggers
+{
+ ///
+ /// Editor GUI for CompositeLogger.
+ ///
+ [CustomEditor(typeof(CompositeLoggerAsset))]
+ public class CompositeLoggerEditor : UnityEditor.Editor
+ {
+ private static readonly string s_description = L10n.Tr("Description");
+ private static readonly string s_descriptionTooltip = L10n.Tr("Description about this logger instance");
+
+ private static readonly string s_loggers = L10n.Tr("Loggers");
+ private static readonly string s_loggersTooltip = L10n.Tr("Loggers to delegates");
+
+ ///
+ public override void OnInspectorGUI()
+ {
+ serializedObject.Update();
+
+ EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(CompositeLoggerAsset.description)),
+ new GUIContent(s_description, s_descriptionTooltip));
+ EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(CompositeLoggerAsset.loggerAssets)),
+ new GUIContent(s_loggers, s_loggersTooltip));
+
+ serializedObject.ApplyModifiedProperties();
+ }
+ }
+}
diff --git a/Editor/UI/Loggers/CompositeLoggerEditor.cs.meta b/Editor/UI/Loggers/CompositeLoggerEditor.cs.meta
new file mode 100644
index 0000000..82151f6
--- /dev/null
+++ b/Editor/UI/Loggers/CompositeLoggerEditor.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 352e692ebfdd40a5827e212bf2d5bd2c
+timeCreated: 1714883649
\ No newline at end of file
diff --git a/Editor/UI/Loggers/ConsoleLoggerEditor.cs b/Editor/UI/Loggers/ConsoleLoggerEditor.cs
new file mode 100644
index 0000000..64a63bf
--- /dev/null
+++ b/Editor/UI/Loggers/ConsoleLoggerEditor.cs
@@ -0,0 +1,35 @@
+// Copyright (c) 2023-2024 DeNA Co., Ltd.
+// This software is released under the MIT License.
+
+using DeNA.Anjin.Loggers;
+using UnityEditor;
+using UnityEngine;
+
+namespace DeNA.Anjin.Editor.UI.Loggers
+{
+ ///
+ /// Editor GUI for ConsoleLogger.
+ ///
+ [CustomEditor(typeof(ConsoleLoggerAsset))]
+ public class ConsoleLoggerEditor : UnityEditor.Editor
+ {
+ private static readonly string s_description = L10n.Tr("Description");
+ private static readonly string s_descriptionTooltip = L10n.Tr("Description about this logger instance");
+
+ private static readonly string s_filterLogType = L10n.Tr("Filter LogType");
+ private static readonly string s_filterLogTypeTooltip = L10n.Tr("To selective enable debug log message");
+
+ ///
+ public override void OnInspectorGUI()
+ {
+ serializedObject.Update();
+
+ EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(ConsoleLoggerAsset.description)),
+ new GUIContent(s_description, s_descriptionTooltip));
+ EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(ConsoleLoggerAsset.filterLogType)),
+ new GUIContent(s_filterLogType, s_filterLogTypeTooltip));
+
+ serializedObject.ApplyModifiedProperties();
+ }
+ }
+}
diff --git a/Editor/UI/Loggers/ConsoleLoggerEditor.cs.meta b/Editor/UI/Loggers/ConsoleLoggerEditor.cs.meta
new file mode 100644
index 0000000..6f4098b
--- /dev/null
+++ b/Editor/UI/Loggers/ConsoleLoggerEditor.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 148502be9aa7474cab0d904b74c2aee4
+timeCreated: 1714865512
\ No newline at end of file
diff --git a/Editor/UI/Loggers/FileLoggerEditor.cs b/Editor/UI/Loggers/FileLoggerEditor.cs
new file mode 100644
index 0000000..53ff52c
--- /dev/null
+++ b/Editor/UI/Loggers/FileLoggerEditor.cs
@@ -0,0 +1,45 @@
+// Copyright (c) 2023-2024 DeNA Co., Ltd.
+// This software is released under the MIT License.
+
+using DeNA.Anjin.Loggers;
+using UnityEditor;
+using UnityEngine;
+
+namespace DeNA.Anjin.Editor.UI.Loggers
+{
+ ///
+ /// Editor GUI for FileLogger.
+ ///
+ [CustomEditor(typeof(FileLoggerAsset))]
+ public class FileLoggerEditor : UnityEditor.Editor
+ {
+ private static readonly string s_description = L10n.Tr("Description");
+ private static readonly string s_descriptionTooltip = L10n.Tr("Description about this logger instance");
+
+ private static readonly string s_outputPath = L10n.Tr("Output File Path");
+ private static readonly string s_outputPathTooltip = L10n.Tr("Log output file path. Specify relative path from project root or absolute path.");
+
+ private static readonly string s_filterLogType = L10n.Tr("Filter LogType");
+ private static readonly string s_filterLogTypeTooltip = L10n.Tr("To selective enable debug log message");
+
+ private static readonly string s_timestamp = L10n.Tr("Timestamp");
+ private static readonly string s_timestampTooltip = L10n.Tr("Output timestamp to log entities");
+
+ ///
+ public override void OnInspectorGUI()
+ {
+ serializedObject.Update();
+
+ EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(FileLoggerAsset.description)),
+ new GUIContent(s_description, s_descriptionTooltip));
+ EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(FileLoggerAsset.outputPath)),
+ new GUIContent(s_outputPath, s_outputPathTooltip));
+ EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(FileLoggerAsset.filterLogType)),
+ new GUIContent(s_filterLogType, s_filterLogTypeTooltip));
+ EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(FileLoggerAsset.timestamp)),
+ new GUIContent(s_timestamp, s_timestampTooltip));
+
+ serializedObject.ApplyModifiedProperties();
+ }
+ }
+}
diff --git a/Editor/UI/Loggers/FileLoggerEditor.cs.meta b/Editor/UI/Loggers/FileLoggerEditor.cs.meta
new file mode 100644
index 0000000..8fce131
--- /dev/null
+++ b/Editor/UI/Loggers/FileLoggerEditor.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 04311af07d024dc0a0f5573bce71c791
+timeCreated: 1714884721
\ No newline at end of file
diff --git a/Editor/UI/Settings/AutopilotSettingsEditor.cs b/Editor/UI/Settings/AutopilotSettingsEditor.cs
index 734c508..2596290 100644
--- a/Editor/UI/Settings/AutopilotSettingsEditor.cs
+++ b/Editor/UI/Settings/AutopilotSettingsEditor.cs
@@ -42,6 +42,10 @@ public class AutopilotSettingsEditor : UnityEditor.Editor
private static readonly string s_junitReportPath = L10n.Tr("JUnit Report Path");
private static readonly string s_junitReportPathTooltip = L10n.Tr("JUnit report output path");
+
+ private static readonly string s_logger = L10n.Tr("Logger");
+ private static readonly string s_loggerTooltip = L10n.Tr("Logger used for this autopilot settings. If omitted, Debug.unityLogger will be used as default.");
+
private static readonly string s_reporter = L10n.Tr("Reporter");
private static readonly string s_reporterTooltip = L10n.Tr("Reporter that called when some errors occurred in target application");
@@ -111,6 +115,8 @@ public override void OnInspectorGUI()
new GUIContent(s_timeScale, s_timeScaleTooltip));
EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(AutopilotSettings.junitReportPath)),
new GUIContent(s_junitReportPath, s_junitReportPathTooltip));
+ EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(AutopilotSettings.loggerAsset)),
+ new GUIContent(s_logger, s_loggerTooltip));
EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(AutopilotSettings.reporter)),
new GUIContent(s_reporter, s_reporterTooltip));
EditorGUILayout.HelpBox(s_obsoletedSlackParamsHelpBox, MessageType.Warning);
diff --git a/README.md b/README.md
index 0106efe..3e97e81 100644
--- a/README.md
+++ b/README.md
@@ -121,17 +121,8 @@ This item can also be overridden from the commandline (see below).
Random SeedSpecify when you want to fix the seed given to the pseudo-random number generator (optional). This is a setting related to the pseudo-random number generator used by the autopilot. To fix the seed of the pseudo-random number generator in the game itself, it is necessary to implement this setting on the game title side.
Time ScaleTime.timeScale. Default is 1.0
JUnit Report PathSpecifies the JUnit format report file output path (optional). If there are zero errors and zero failures, the autopilot run is considered to have completed successfully.
- Slack TokenWeb API token used for Slack notifications (if omitted, no notifications will be sent)
- Slack ChannelsChannels to send Slack notifications (not notified if omitted. Multiple channels can be specified by separating them with commas)
-
-
-#### Slack Mention Settings
-
-Set the mentions to be given to Slack notifications.
-
-
- - Mention Sub Team IDs
- Comma Separated Team IDs to Mention in Slack Notification Message
- - Add Here In Slack Message
- Add @here to Slack notification message. Default is off
+ - Logger
- Logger used for this autopilot settings. If omitted,
Debug.unityLogger
will be used as default.
+ - Reporter
- Reporter that called when some errors occurred in target application
#### Error Handling Settings
@@ -152,10 +143,29 @@ Set up a filter to catch abnormal log messages and notify Slack.
Whether you use the built-in Agent or implement custom Agent, you must create an instance (.asset file) of it in the Unity editor.
Instances are created by right-clicking in the Project window of the Unity editor to open the context menu, then selecting
-**Create > Anjin > Agent name**.
+**Create > Anjin > Agent type name**.
Select the generated file, Agent-specific settings are displayed in the inspector and can be customized.
-You can prepare multiple Agents with different settings for the same Agent and use them in different Scenes.
+You can prepare multiple Agents with different settings for the same Agent type and use them in different Scenes.
+
+
+### Generate and configure the Logger setting file (.asset)
+
+Logger instances are created by right-clicking in the Project window of the Unity editor to open the context menu, then selecting
+**Create > Anjin > Logger type name**.
+
+Select the generated file, Logger-specific settings are displayed in the inspector and can be customized.
+You can prepare multiple Loggers with different settings for the same Logger type.
+
+
+### Generate and configure the Reporter setting file (.asset)
+
+Reporter instances are created by right-clicking in the Project window of the Unity editor to open the context menu, then selecting
+**Create > Anjin > Reporter type name**.
+
+Select the generated file, Reporter-specific settings are displayed in the inspector and can be customized.
+You can prepare multiple Reporters with different settings for the same Reporter type.
+
## Run autopilot
@@ -214,8 +224,6 @@ For details on each argument, see the entry of the same name in the "Generate an
RANDOM_SEEDSpecifies when you want to fix the seed given to the pseudo-random number generator
TIME_SCALESpecifies the Time.timeScale. Default is 1.0
JUNIT_REPORT_PATHSpecifies the JUnit-style report file output path
- SLACK_TOKENWeb API token used for Slack notifications
- SLACK_CHANNELSChannels to send Slack notifications
In both cases, the key should be prefixed with `-` and specified as `-LIFESPAN_SEC 60`.
@@ -224,7 +232,7 @@ In both cases, the key should be prefixed with `-` and specified as `-LIFESPAN_S
## Built-in Agents
-The following Agents are provided. These can be used as they are, or project-specific Agents can be implemented and used.
+The following Agent types are provided. These can be used as they are, or project-specific Agents can be implemented and used.
### UGUIMonkeyAgent
@@ -370,6 +378,78 @@ This can be accomplished with `ParallelCompositeAgent`, but it is easier to set
+## Built-in Logger
+
+The following Logger types are provided. These can be used as they are, or project-specific Loggers can be implemented and used.
+
+
+### Composite Logger
+
+A Logger that delegates to multiple loggers.
+
+The instance of this Logger (.asset file) can have the following settings.
+
+
+ - Loggers
- A list of Logger to delegates
+
+
+
+### Console Logger
+
+A Logger that outputs to a console.
+
+The instance of this Logger (.asset file) can have the following settings.
+
+
+ - Filter LogType
- To selective enable debug log message
+
+
+
+### File Logger
+
+A Logger that outputs to a specified file.
+
+The instance of this Logger (.asset file) can have the following settings.
+
+
+ - Output File Path
- Log output file path. Specify relative path from project root or absolute path. When run on player, it will be the
Application.persistentDataPath
.
+ - Filter LogType
- To selective enable debug log message
+ - Timestamp
- Output timestamp to log entities
+
+
+
+
+## Built-in Reporter
+
+The following Reporter types are provided. These can be used as they are, or project-specific Reporters can be implemented and used.
+
+
+### Composite Reporter
+
+A Reporter that delegates to multiple Reporters.
+
+The instance of this Reporter (.asset file) can have the following settings.
+
+
+ - Reporters
- A list of Reporter to delegates
+
+
+
+### Slack Reporter
+
+A Reporter that post report to Slack.
+
+The instance of this Reporter (.asset file) can have the following settings.
+
+
+ - Slack Token
- Web API token used for Slack notifications (if omitted, no notifications will be sent)
+ - Slack Channels
- Channels to send Slack notifications (not notified if omitted. Multiple channels can be specified by separating them with commas)
+ - Mention Sub Team IDs
- Comma Separated Team IDs to Mention in Slack Notification Message
+ - Add Here In Slack Message
- Add @here to Slack notification message. Default is off
+
+
+
+
## Implementation of game title-specific code
Game title specific Agents and initialization code must be avoided in the release build.
diff --git a/README_ja.md b/README_ja.md
index f8af08d..bb38a33 100644
--- a/README_ja.md
+++ b/README_ja.md
@@ -118,17 +118,8 @@ v1.0.0時点では `EmergencyExitAgent` の使用を想定しています。
Random Seed疑似乱数発生器に与えるシードを固定したいときに指定します(省略可)。なお、これはオートパイロットの使用する疑似乱数発生器に関する設定であり、ゲーム本体の疑似乱数発生器シードを固定するにはタイトル側での実装が必要です。
Time ScaleTime.timeScaleを指定します。デフォルトは1.0
JUnit Report PathJUnit形式のレポートファイル出力パスを指定します(省略可)。オートパイロット実行の成否は、Unityエディターの終了コードでなくこのファイルを見て判断するのが確実です。errors, failuresともに0件であれば正常終了と判断できます。
- Slack TokenSlack通知に使用するWeb APIトークン(省略時は通知されない)
- Slack ChannelsSlack通知を送るチャンネル(省略時は通知されない。カンマ区切りで複数指定対応)
-
-
-#### Slackメンション設定
-
-Slack通知に付与するメンションを設定します。
-
-
- - Mention Sub Team IDs
- Slack通知メッセージでメンションするチームのIDをカンマ区切りで指定します
- - Add Here In Slack Message
- Slack通知メッセージに@hereを付けます。デフォルトはoff
+ - Logger
- オートパイロットが使用するロガー指定します。省略時は
Debug.unityLogger
がデフォルトとして使用されます
+ - Reporter
- 対象のアプリケーションで発生したエラーを通知するレポータを指定します
#### エラーハンドリング設定
@@ -156,6 +147,26 @@ Slack通知に付与するメンションを設定します。
同じAgentでも設定の違うものを複数用意して、Sceneによって使い分けることができます。
+### ロガー設定ファイル(.asset)の生成
+
+ロガーインスタンスは、UnityエディタのProjectウィンドウで右クリックしてコンテキストメニューを開き、
+**Create > Anjin > Logger名**
+を選択すると生成できます。ファイル名は任意です。
+
+生成したファイルを選択すると、インスペクタにロガー固有の設定項目が表示され、カスタマイズが可能です。
+同じロガーでも設定の違うものを複数用意して使い分けることができます。
+
+
+### レポータ設定ファイル(.asset)の生成
+
+レポータインスタンスは、UnityエディタのProjectウィンドウで右クリックしてコンテキストメニューを開き、
+**Create > Anjin > Reporter名**
+を選択すると生成できます。ファイル名は任意です。
+
+生成したファイルを選択すると、インスペクタにレポータ固有の設定項目が表示され、カスタマイズが可能です。
+同じレポータでも設定の違うものを複数用意して使い分けることができます。
+
+
## 実行方法
@@ -215,8 +226,6 @@ $(UNITY) \
RANDOM_SEED疑似乱数発生器に与えるシードを固定したいときに指定します
TIME_SCALETime.timeScaleを指定します。デフォルトは1.0
JUNIT_REPORT_PATHJUnit形式のレポートファイル出力パスを指定します
- SLACK_TOKENSlack通知に使用するWeb APIトークン
- SLACK_CHANNELSSlack通知を送るチャンネル
いずれも、キーの先頭に`-`を付けて`-LIFESPAN_SEC 60`のように指定してください。
@@ -373,6 +382,78 @@ SerialCompositeAgentと組み合わせることで、シナリオを何周もし
+## ビルトイン ロガー
+
+以下のロガータイプが用意されています。これらをそのまま使用することも、プロジェクト独自のロガーを実装して使用することも可能です。
+
+
+### Composite Logger
+
+複数のロガーを登録し、そのすべてにログ出力を委譲するロガーです。
+
+このロガーのインスタンス(.assetファイル)には以下を設定できます。
+
+
+ - Loggers
- ログ出力を委譲するLoggerのリスト
+
+
+
+### Console Logger
+
+ログをコンソールに出力するロガーです。
+
+このロガーのインスタンス(.assetファイル)には以下を設定できます。
+
+
+ - フィルタリングLogType
- 選択したLogType以上のログ出力のみを有効にします
+
+
+
+### File Logger
+
+ログを指定ファイルに出力するロガーです。
+
+このロガーのインスタンス(.assetファイル)には以下を設定できます。
+
+
+ - 出力ファイルパス
- ログ出力ファイルのパス。プロジェクトルートからの相対パスまたは絶対パスを指定します。プレイヤー実行では相対パスの起点は
Application.persistentDataPath
になります。
+ - フィルタリングLogType
- 選択したLogType以上のログ出力のみを有効にします
+ - タイムスタンプを出力
- ログエンティティにタイムスタンプを出力します
+
+
+
+
+## ビルトイン レポータ
+
+以下のレポータタイプが用意されています。これらをそのまま使用することも、プロジェクト独自のレポータを実装して使用することも可能です。
+
+
+### Composite Reporter
+
+複数のレポータを登録し、そのすべてにレポート送信を委譲するレポータです。
+
+このレポータのインスタンス(.assetファイル)には以下を設定できます。
+
+
+ - Reporters
- レポート送信を委譲するReporterのリスト
+
+
+
+### Slack Reporter
+
+Slackにレポート送信するレポータです。
+
+このレポータのインスタンス(.assetファイル)には以下を設定できます。
+
+
+ - Slack Token
- Slack通知に使用するWeb APIトークン(省略時は通知されない)
+ - Slack Channels
- Slack通知を送るチャンネル(省略時は通知されない。カンマ区切りで複数指定対応)
+ - Mention Sub Team IDs
- Slack通知メッセージでメンションするチームのIDをカンマ区切りで指定します
+ - Add Here In Slack Message
- Slack通知メッセージに@hereを付けます。デフォルトはoff
+
+
+
+
## ゲームタイトル独自処理の実装
ゲームタイトル固有のAgent等を実装する場合、リリースビルドへの混入を避けるため、専用のアセンブリに分けることをおすすめします。
diff --git a/Runtime/Autopilot.cs b/Runtime/Autopilot.cs
index 252013e..8251569 100644
--- a/Runtime/Autopilot.cs
+++ b/Runtime/Autopilot.cs
@@ -5,6 +5,7 @@
using System.Collections;
using System.Threading;
using Cysharp.Threading.Tasks;
+using DeNA.Anjin.Loggers;
using DeNA.Anjin.Settings;
using DeNA.Anjin.Utilities;
using UnityEngine;
@@ -18,6 +19,7 @@ namespace DeNA.Anjin
///
public class Autopilot : MonoBehaviour
{
+ private AbstractLoggerAsset _loggerAsset;
private ILogger _logger;
private RandomFactory _randomFactory;
private IAgentDispatcher _dispatcher;
@@ -32,7 +34,8 @@ private void Start()
_settings = _state.settings;
Assert.IsNotNull(_settings);
- _logger = CreateLogger();
+ _loggerAsset = _settings.loggerAsset;
+ _logger = _loggerAsset != null ? _loggerAsset.Logger : CreateDefaultLogger();
if (!int.TryParse(_settings.randomSeed, out var seed))
{
@@ -61,15 +64,17 @@ private void Start()
}
_startTime = Time.realtimeSinceStartup;
+ _logger.Log("Launched autopilot");
}
///
- /// Returns an agent dispatcher that autopilot uses. You can change a logger by overriding this method
+ /// Returns an logger that autopilot uses. You can change a logger by overriding this method.
+ /// Default logger is that write to console.
///
- /// A new logger
- protected virtual ILogger CreateLogger()
+ /// A new logger that write to console
+ protected virtual ILogger CreateDefaultLogger()
{
- return new ConsoleLogger(Debug.unityLogger.logHandler);
+ return Debug.unityLogger;
}
///
@@ -88,15 +93,9 @@ private void OnDestroy()
// Clear event listeners.
// When play mode is stopped by the user, onDestroy calls without TerminateAsync.
- if (_dispatcher != null)
- {
- _dispatcher.Dispose();
- }
-
- if (_logMessageHandler != null)
- {
- _logMessageHandler.Dispose();
- }
+ _dispatcher?.Dispose();
+ _logMessageHandler?.Dispose();
+ _settings.loggerAsset?.Dispose();
}
///
@@ -116,6 +115,11 @@ public async UniTask TerminateAsync(ExitCode exitCode, string logString = null,
JUnitReporter.Output(_state.settings.junitReportPath, (int)exitCode, logString, stackTrace, time);
}
+ if (exitCode != ExitCode.Normally)
+ {
+ _logger.Log(LogType.Error, logString + Environment.NewLine + stackTrace);
+ }
+
Destroy(this.gameObject);
if (_state.IsLaunchFromPlayMode)
diff --git a/Runtime/Loggers.meta b/Runtime/Loggers.meta
new file mode 100644
index 0000000..20ba159
--- /dev/null
+++ b/Runtime/Loggers.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 333ef1bee73a4afeb213d974170d69f7
+timeCreated: 1714805458
\ No newline at end of file
diff --git a/Runtime/Loggers/AbstractLoggerAsset.cs b/Runtime/Loggers/AbstractLoggerAsset.cs
new file mode 100644
index 0000000..820c7e3
--- /dev/null
+++ b/Runtime/Loggers/AbstractLoggerAsset.cs
@@ -0,0 +1,29 @@
+// Copyright (c) 2023-2024 DeNA Co., Ltd.
+// This software is released under the MIT License.
+
+using System;
+using UnityEngine;
+
+namespace DeNA.Anjin.Loggers
+{
+ ///
+ /// Abstract logger settings used for autopilot.
+ ///
+ public abstract class AbstractLoggerAsset : ScriptableObject, IDisposable
+ {
+#if UNITY_EDITOR
+ ///
+ /// Description about this agent instance.
+ ///
+ [Multiline] public string description;
+#endif
+
+ ///
+ /// Logger implementation used for autopilot.
+ ///
+ public abstract ILogger Logger { get; }
+
+ ///
+ public abstract void Dispose();
+ }
+}
diff --git a/Runtime/Loggers/AbstractLoggerAsset.cs.meta b/Runtime/Loggers/AbstractLoggerAsset.cs.meta
new file mode 100644
index 0000000..a0f5626
--- /dev/null
+++ b/Runtime/Loggers/AbstractLoggerAsset.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 09c42462b5c84179ad2e8060a77ddd2a
+timeCreated: 1714805641
\ No newline at end of file
diff --git a/Runtime/Loggers/CompositeLoggerAsset.cs b/Runtime/Loggers/CompositeLoggerAsset.cs
new file mode 100644
index 0000000..21bffdb
--- /dev/null
+++ b/Runtime/Loggers/CompositeLoggerAsset.cs
@@ -0,0 +1,87 @@
+// Copyright (c) 2023-2024 DeNA Co., Ltd.
+// This software is released under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+using Object = UnityEngine.Object;
+
+namespace DeNA.Anjin.Loggers
+{
+ ///
+ /// A class for a logger that delegates to multiple loggers
+ ///
+ [CreateAssetMenu(fileName = "New CompositeLogger", menuName = "Anjin/Composite Logger", order = 70)]
+ public class CompositeLoggerAsset : AbstractLoggerAsset
+ {
+ ///
+ /// Loggers to delegates
+ ///
+ public List loggerAssets = new List();
+
+ private CompositeLogHandler _handler;
+ private Logger _logger;
+
+ ///
+ public override ILogger Logger
+ {
+ get
+ {
+ if (_logger != null)
+ {
+ return _logger;
+ }
+
+ _handler = new CompositeLogHandler(loggerAssets, this);
+ _logger = new Logger(_handler);
+ return _logger;
+ }
+ }
+
+ ///
+ public override void Dispose()
+ {
+ _handler?.Dispose();
+ }
+
+ private class CompositeLogHandler : ILogHandler, IDisposable
+ {
+ private readonly List _loggerAssets;
+ private readonly AbstractLoggerAsset _owner;
+
+ public CompositeLogHandler(List loggerAssets, AbstractLoggerAsset owner)
+ {
+ _loggerAssets = loggerAssets;
+ _owner = owner;
+ }
+
+ ///
+ public void LogFormat(LogType logType, Object context, string format, params object[] args)
+ {
+ foreach (var loggerAsset in _loggerAssets.Where(x => x != null && x != _owner))
+ {
+ loggerAsset.Logger.LogFormat(logType, context, format, args);
+ }
+ }
+
+ ///
+ public void LogException(Exception exception, Object context)
+ {
+ foreach (var loggerAsset in _loggerAssets.Where(x => x != null && x != _owner))
+ {
+ loggerAsset.Logger.LogException(exception, context);
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ foreach (var loggerAsset in _loggerAssets.Where(x => x != null && x != _owner))
+ {
+ loggerAsset.Dispose();
+ }
+ }
+ }
+ }
+}
diff --git a/Runtime/Loggers/CompositeLoggerAsset.cs.meta b/Runtime/Loggers/CompositeLoggerAsset.cs.meta
new file mode 100644
index 0000000..970dca3
--- /dev/null
+++ b/Runtime/Loggers/CompositeLoggerAsset.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 5a46fe0a596d461bb0d039469d2b9ab4
+timeCreated: 1714880656
\ No newline at end of file
diff --git a/Runtime/Loggers/ConsoleLoggerAsset.cs b/Runtime/Loggers/ConsoleLoggerAsset.cs
new file mode 100644
index 0000000..4d61dfc
--- /dev/null
+++ b/Runtime/Loggers/ConsoleLoggerAsset.cs
@@ -0,0 +1,43 @@
+// Copyright (c) 2023-2024 DeNA Co., Ltd.
+// This software is released under the MIT License.
+
+using UnityEngine;
+
+namespace DeNA.Anjin.Loggers
+{
+ ///
+ /// A Logger that outputs to a console.
+ /// Logger using Debug.unityLogger
+ ///
+ [CreateAssetMenu(fileName = "New ConsoleLogger", menuName = "Anjin/Console Logger", order = 71)]
+ public class ConsoleLoggerAsset : AbstractLoggerAsset
+ {
+ ///
+ /// To selective enable debug log message.
+ ///
+ public LogType filterLogType = LogType.Log;
+
+ private ILogger _logger;
+
+ ///
+ public override ILogger Logger
+ {
+ get
+ {
+ if (_logger != null)
+ {
+ return _logger;
+ }
+
+ _logger = new Logger(Debug.unityLogger.logHandler) { filterLogType = filterLogType };
+ return _logger;
+ }
+ }
+
+ ///
+ public override void Dispose()
+ {
+ // Nothing to dispose.
+ }
+ }
+}
diff --git a/Runtime/Loggers/ConsoleLoggerAsset.cs.meta b/Runtime/Loggers/ConsoleLoggerAsset.cs.meta
new file mode 100644
index 0000000..b58e9e5
--- /dev/null
+++ b/Runtime/Loggers/ConsoleLoggerAsset.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: e5b0f8b0450fb478d95fe8ea52ea83ff
\ No newline at end of file
diff --git a/Runtime/Loggers/FileLoggerAsset.cs b/Runtime/Loggers/FileLoggerAsset.cs
new file mode 100644
index 0000000..9cbc3a8
--- /dev/null
+++ b/Runtime/Loggers/FileLoggerAsset.cs
@@ -0,0 +1,141 @@
+// Copyright (c) 2023-2024 DeNA Co., Ltd.
+// This software is released under the MIT License.
+
+using System;
+using System.IO;
+using UnityEngine;
+using Object = UnityEngine.Object;
+
+namespace DeNA.Anjin.Loggers
+{
+ ///
+ /// A Logger that outputs to a specified file.
+ ///
+ ///
+ /// If you want more functionality in the future, consider using the Unity Logging package (com.unity.logging) or ZLogger.
+ ///
+ ///
+ ///
+ [CreateAssetMenu(fileName = "New FileLogger", menuName = "Anjin/File Logger", order = 72)]
+ public class FileLoggerAsset : AbstractLoggerAsset
+ {
+ ///
+ /// Log output file path.
+ /// Note: relative path from the project root directory. When run on player, it will be the Application.persistentDataPath.
+ ///
+ public string outputPath;
+
+ ///
+ /// To selective enable debug log message.
+ ///
+ public LogType filterLogType = LogType.Log;
+
+ ///
+ /// Output timestamp to log entities.
+ ///
+ public bool timestamp = true;
+
+ private FileLogHandler _handler;
+ private ILogger _logger;
+
+ ///
+ public override ILogger Logger
+ {
+ get
+ {
+ if (_logger != null)
+ {
+ return _logger;
+ }
+
+ var directory = Path.GetDirectoryName(outputPath);
+ if (directory != null && !Directory.Exists(directory))
+ {
+ Directory.CreateDirectory(directory);
+ }
+
+ _handler = new FileLogHandler(outputPath, timestamp);
+ _logger = new Logger(_handler) { filterLogType = filterLogType };
+ return _logger;
+ }
+ }
+
+ ///
+ public override void Dispose()
+ {
+ _handler?.Dispose();
+ }
+
+ private class TimestampCache
+ {
+ private string _timestampCache;
+ private int _timestampCacheFrame;
+
+ public string GetTimestamp()
+ {
+ if (_timestampCacheFrame != Time.frameCount)
+ {
+ _timestampCache = DateTime.Now.ToString("[HH:mm:ss.fff] ");
+ _timestampCacheFrame = Time.frameCount;
+ }
+
+ return _timestampCache;
+ }
+ }
+
+ private class FileLogHandler : ILogHandler, IDisposable
+ {
+ private readonly StreamWriter _writer;
+ private readonly bool _timestamp;
+ private readonly TimestampCache _timestampCache;
+ private bool _disposed;
+
+ public FileLogHandler(string outputPath, bool timestamp)
+ {
+ _writer = new StreamWriter(outputPath, false);
+ _writer.AutoFlush = true;
+ _timestamp = timestamp;
+ if (_timestamp)
+ {
+ _timestampCache = new TimestampCache();
+ }
+ }
+
+ public void LogFormat(LogType logType, Object context, string format, params object[] args)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (_timestamp)
+ {
+ _writer.Write(_timestampCache.GetTimestamp());
+ }
+
+ _writer.WriteLine(format, args);
+ }
+
+ public void LogException(Exception exception, Object context)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (_timestamp)
+ {
+ _writer.Write(_timestampCache.GetTimestamp());
+ }
+
+ _writer.WriteLine("{0}", new object[] { exception.ToString() });
+ }
+
+ public void Dispose()
+ {
+ _writer?.Dispose();
+ _disposed = true;
+ }
+ }
+ }
+}
diff --git a/Runtime/Loggers/FileLoggerAsset.cs.meta b/Runtime/Loggers/FileLoggerAsset.cs.meta
new file mode 100644
index 0000000..42a2923
--- /dev/null
+++ b/Runtime/Loggers/FileLoggerAsset.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 1efc191684b0450db1298fe7a4c4f1b2
+timeCreated: 1714882848
\ No newline at end of file
diff --git a/Runtime/Settings/AutopilotSettings.cs b/Runtime/Settings/AutopilotSettings.cs
index 78279a0..9d58170 100644
--- a/Runtime/Settings/AutopilotSettings.cs
+++ b/Runtime/Settings/AutopilotSettings.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using DeNA.Anjin.Agents;
+using DeNA.Anjin.Loggers;
using DeNA.Anjin.Reporters;
using UnityEngine;
@@ -125,11 +126,16 @@ public class AutopilotSettings : ScriptableObject
///
public string[] ignoreMessages;
+ ///
+ /// Logger used for this autopilot settings.
+ ///
+ public AbstractLoggerAsset loggerAsset;
+
///
/// Reporter that called when some errors occurred
///
public AbstractReporter reporter;
-
+
///
/// Overwrites specified values in the command line arguments
///
diff --git a/Runtime/Utilities/ConsoleLogger.cs b/Runtime/Utilities/ConsoleLogger.cs
deleted file mode 100644
index 2732926..0000000
--- a/Runtime/Utilities/ConsoleLogger.cs
+++ /dev/null
@@ -1,133 +0,0 @@
-// Copyright (c) 2023 DeNA Co., Ltd.
-// This software is released under the MIT License.
-
-using System;
-using UnityEngine;
-using Object = UnityEngine.Object;
-
-namespace DeNA.Anjin.Utilities
-{
- ///
- /// Logger using Debug.unityLogger
- ///
- public class ConsoleLogger : ILogger
- {
- private readonly ILogger _loggerImpl = Debug.unityLogger;
-
- ///
- public ILogHandler logHandler { get; set; }
-
- ///
- public bool logEnabled { get; set; }
-
- ///
- public LogType filterLogType { get; set; }
-
- ///
- /// Constructor
- ///
- ///
- public ConsoleLogger(ILogHandler handler)
- {
- this.logHandler = handler;
- this.logEnabled = true;
- this.filterLogType = LogType.Log;
- }
-
- ///
- public void LogFormat(LogType logType, Object context, string format, params object[] args)
- {
- _loggerImpl.LogFormat(logType, context, format, args);
- }
-
- ///
- public void LogException(Exception exception, Object context)
- {
- _loggerImpl.LogException(exception, context);
- }
-
- ///
- public bool IsLogTypeAllowed(LogType logType)
- {
- return _loggerImpl.IsLogTypeAllowed(logType);
- }
-
- ///
- public void Log(LogType logType, object message)
- {
- _loggerImpl.Log(logType, message);
- }
-
- ///
- public void Log(LogType logType, object message, Object context)
- {
- _loggerImpl.Log(logType, message, context);
- }
-
- ///
- public void Log(LogType logType, string tag, object message)
- {
- _loggerImpl.Log(logType, tag, message);
- }
-
- ///
- public void Log(LogType logType, string tag, object message, Object context)
- {
- _loggerImpl.Log(logType, tag, message, context);
- }
-
- ///
- public void Log(object message)
- {
- _loggerImpl.Log(message);
- }
-
- ///
- public void Log(string tag, object message)
- {
- _loggerImpl.Log(tag, message);
- }
-
- ///
- public void Log(string tag, object message, Object context)
- {
- _loggerImpl.Log(tag, message, context);
- }
-
- ///
- public void LogWarning(string tag, object message)
- {
- _loggerImpl.LogWarning(tag, message);
- }
-
- ///
- public void LogWarning(string tag, object message, Object context)
- {
- _loggerImpl.LogWarning(tag, message, context);
- }
-
- ///
- public void LogError(string tag, object message)
- {
- _loggerImpl.LogError(tag, message);
- }
-
- ///
- public void LogError(string tag, object message, Object context)
- {
- _loggerImpl.LogError(tag, message, context);
- }
-
- ///
- public void LogFormat(LogType logType, string format, params object[] args)
- {
- _loggerImpl.LogFormat(logType, format, args);
- }
-
- ///
- public void LogException(Exception exception)
- {
- _loggerImpl.LogException(exception);
- }
- }
-}
diff --git a/Runtime/Utilities/ConsoleLogger.cs.meta b/Runtime/Utilities/ConsoleLogger.cs.meta
deleted file mode 100644
index 9ff365a..0000000
--- a/Runtime/Utilities/ConsoleLogger.cs.meta
+++ /dev/null
@@ -1,3 +0,0 @@
-fileFormatVersion: 2
-guid: 563470594a76466aa6a66ed3dbaa14f1
-timeCreated: 1618219385
\ No newline at end of file
diff --git a/Tests/Runtime/AgentDispatcherTest.cs b/Tests/Runtime/AgentDispatcherTest.cs
index 9d88356..7686a5e 100644
--- a/Tests/Runtime/AgentDispatcherTest.cs
+++ b/Tests/Runtime/AgentDispatcherTest.cs
@@ -58,7 +58,7 @@ private static SpyAliveCountAgent CreateSpyAliveCountAgent(string name = nameof(
private void SetUpDispatcher(AutopilotSettings settings)
{
- var logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ var logger = Debug.unityLogger;
var randomFactory = new RandomFactory(0);
_dispatcher = new AgentDispatcher(settings, logger, randomFactory);
diff --git a/Tests/Runtime/Agents/DoNothingAgentTest.cs b/Tests/Runtime/Agents/DoNothingAgentTest.cs
index 8e4f48e..6ea92a9 100644
--- a/Tests/Runtime/Agents/DoNothingAgentTest.cs
+++ b/Tests/Runtime/Agents/DoNothingAgentTest.cs
@@ -18,7 +18,7 @@ public class DoNothingAgentTest
public async Task Run_cancelTask_stopAgent()
{
var agent = ScriptableObject.CreateInstance();
- agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ agent.Logger = Debug.unityLogger;
agent.Random = new RandomFactory(0).CreateRandom();
agent.name = nameof(Run_cancelTask_stopAgent);
agent.lifespanSec = 0; // Expect indefinite execution
@@ -41,7 +41,7 @@ public async Task Run_cancelTask_stopAgent()
public async Task Run_lifespanPassed_stopAgent()
{
var agent = ScriptableObject.CreateInstance();
- agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ agent.Logger = Debug.unityLogger;
agent.Random = new RandomFactory(0).CreateRandom();
agent.name = nameof(Run_lifespanPassed_stopAgent);
agent.lifespanSec = 1;
diff --git a/Tests/Runtime/Agents/EmergencyExitAgentTest.cs b/Tests/Runtime/Agents/EmergencyExitAgentTest.cs
index dbff6b2..6c92ca6 100644
--- a/Tests/Runtime/Agents/EmergencyExitAgentTest.cs
+++ b/Tests/Runtime/Agents/EmergencyExitAgentTest.cs
@@ -23,7 +23,7 @@ public class EmergencyExitAgentTest
public async Task Run_cancelTask_stopAgent()
{
var agent = ScriptableObject.CreateInstance();
- agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ agent.Logger = Debug.unityLogger;
agent.Random = new RandomFactory(0).CreateRandom();
agent.name = nameof(Run_cancelTask_stopAgent);
@@ -53,7 +53,7 @@ private static SpyButton CreateButtonWithEmergencyExitAnnotation(bool interactab
public async Task Run_existEmergencyExitButton_ClickEmergencyExitButton()
{
var agent = ScriptableObject.CreateInstance();
- agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ agent.Logger = Debug.unityLogger;
agent.Random = new RandomFactory(0).CreateRandom();
agent.name = nameof(Run_cancelTask_stopAgent);
@@ -81,7 +81,7 @@ public async Task Run_existEmergencyExitButton_ClickEmergencyExitButton()
public async Task Run_existNotInteractableEmergencyExitButton_DoesNotClickEmergencyExitButton()
{
var agent = ScriptableObject.CreateInstance();
- agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ agent.Logger = Debug.unityLogger;
agent.Random = new RandomFactory(0).CreateRandom();
agent.name = nameof(Run_cancelTask_stopAgent);
diff --git a/Tests/Runtime/Agents/OneTimeAgentTest.cs b/Tests/Runtime/Agents/OneTimeAgentTest.cs
index 6c619f0..eab1cf6 100644
--- a/Tests/Runtime/Agents/OneTimeAgentTest.cs
+++ b/Tests/Runtime/Agents/OneTimeAgentTest.cs
@@ -33,7 +33,7 @@ public async Task Run_cancelTask_stopAgent()
var childAgent = CreateChildAgent(5000);
var agent = ScriptableObject.CreateInstance();
- agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ agent.Logger = Debug.unityLogger;
agent.Random = new RandomFactory(0).CreateRandom();
agent.name = nameof(Run_cancelTask_stopAgent);
agent.agent = childAgent;
@@ -58,7 +58,7 @@ public async Task Run_markWasExecuted()
var childAgent = CreateChildAgent(100);
var agent = ScriptableObject.CreateInstance();
- agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ agent.Logger = Debug.unityLogger;
agent.Random = new RandomFactory(0).CreateRandom();
agent.name = nameof(Run_cancelTask_stopAgent);
agent.agent = childAgent;
@@ -83,7 +83,7 @@ public async Task Run_wasExecuted_notExecuteChildAgent()
var childAgent = CreateChildAgent(100);
var agent = ScriptableObject.CreateInstance();
- agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ agent.Logger = Debug.unityLogger;
agent.Random = new RandomFactory(0).CreateRandom();
agent.name = nameof(Run_cancelTask_stopAgent);
agent.agent = childAgent;
@@ -108,7 +108,7 @@ public async Task Run_setLoggerAndRandomInstanceToChildAgent()
var childAgent = CreateChildAgent(100);
var agent = ScriptableObject.CreateInstance();
- agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ agent.Logger = Debug.unityLogger;
agent.Random = new RandomFactory(0).CreateRandom();
agent.name = nameof(Run_cancelTask_stopAgent);
agent.agent = childAgent;
diff --git a/Tests/Runtime/Agents/ParallelCompositeAgentTest.cs b/Tests/Runtime/Agents/ParallelCompositeAgentTest.cs
index 8a0d652..f181178 100644
--- a/Tests/Runtime/Agents/ParallelCompositeAgentTest.cs
+++ b/Tests/Runtime/Agents/ParallelCompositeAgentTest.cs
@@ -35,7 +35,7 @@ public async Task Run_cancelTask_stopAgent()
var lastChildAgent = CreateChildAgent(500);
var agent = ScriptableObject.CreateInstance();
- agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ agent.Logger = Debug.unityLogger;
agent.Random = new RandomFactory(0).CreateRandom();
agent.name = nameof(Run_cancelTask_stopAgent);
agent.agents = new List() { firstChildAgent, secondChildAgent, lastChildAgent };
@@ -67,7 +67,7 @@ public async Task Run_lifespanPassed_stopAgent()
var lastChildAgent = CreateChildAgent(500);
var agent = ScriptableObject.CreateInstance();
- agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ agent.Logger = Debug.unityLogger;
agent.Random = new RandomFactory(0).CreateRandom();
agent.name = nameof(Run_lifespanPassed_stopAgent);
agent.agents = new List() { firstChildAgent, secondChildAgent, lastChildAgent };
@@ -96,7 +96,7 @@ public async Task Run_setLoggerAndRandomInstanceToChildAgent()
var lastChildAgent = CreateChildAgent(500);
var agent = ScriptableObject.CreateInstance();
- agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ agent.Logger = Debug.unityLogger;
agent.Random = new RandomFactory(0).CreateRandom();
agent.name = nameof(Run_setLoggerAndRandomInstanceToChildAgent);
agent.agents = new List() { firstChildAgent, secondChildAgent, lastChildAgent };
diff --git a/Tests/Runtime/Agents/RepeatAgentTest.cs b/Tests/Runtime/Agents/RepeatAgentTest.cs
index 7565b00..9637d2f 100644
--- a/Tests/Runtime/Agents/RepeatAgentTest.cs
+++ b/Tests/Runtime/Agents/RepeatAgentTest.cs
@@ -31,7 +31,7 @@ public async Task Run_cancelTask_stopAgent()
var childAgent = CreateChildAgent(100);
var agent = ScriptableObject.CreateInstance();
- agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ agent.Logger = Debug.unityLogger;
agent.Random = new RandomFactory(0).CreateRandom();
agent.name = nameof(Run_cancelTask_stopAgent);
agent.agent = childAgent;
@@ -56,7 +56,7 @@ public async Task Run_childAgentRunRepeating()
var childAgent = CreateChildAgent(100);
var agent = ScriptableObject.CreateInstance();
- agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ agent.Logger = Debug.unityLogger;
agent.Random = new RandomFactory(0).CreateRandom();
agent.name = nameof(Run_cancelTask_stopAgent);
agent.agent = childAgent;
@@ -82,7 +82,7 @@ public async Task Run_setLoggerAndRandomInstanceToChildAgent()
var childAgent = CreateChildAgent(100);
var agent = ScriptableObject.CreateInstance();
- agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ agent.Logger = Debug.unityLogger;
agent.Random = new RandomFactory(0).CreateRandom();
agent.name = nameof(Run_cancelTask_stopAgent);
agent.agent = childAgent;
diff --git a/Tests/Runtime/Agents/SerialCompositeAgentTest.cs b/Tests/Runtime/Agents/SerialCompositeAgentTest.cs
index 2f4fdda..7d3b947 100644
--- a/Tests/Runtime/Agents/SerialCompositeAgentTest.cs
+++ b/Tests/Runtime/Agents/SerialCompositeAgentTest.cs
@@ -35,7 +35,7 @@ public async Task Run_cancelTask_stopAgent()
var lastChildAgent = CreateChildAgent(500);
var agent = ScriptableObject.CreateInstance();
- agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ agent.Logger = Debug.unityLogger;
agent.Random = new RandomFactory(0).CreateRandom();
agent.name = nameof(Run_cancelTask_stopAgent);
agent.agents = new List() { firstChildAgent, secondChildAgent, lastChildAgent };
@@ -67,7 +67,7 @@ public async Task Run_lifespanPassed_stopAgent()
var lastChildAgent = CreateChildAgent(500);
var agent = ScriptableObject.CreateInstance();
- agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ agent.Logger = Debug.unityLogger;
agent.Random = new RandomFactory(0).CreateRandom();
agent.name = nameof(Run_lifespanPassed_stopAgent);
agent.agents = new List() { firstChildAgent, secondChildAgent, lastChildAgent };
@@ -96,7 +96,7 @@ public async Task Run_setLoggerAndRandomInstanceToChildAgent()
var lastChildAgent = CreateChildAgent(500);
var agent = ScriptableObject.CreateInstance();
- agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ agent.Logger = Debug.unityLogger;
agent.Random = new RandomFactory(0).CreateRandom();
agent.name = nameof(Run_setLoggerAndRandomInstanceToChildAgent);
agent.agents = new List() { firstChildAgent, secondChildAgent, lastChildAgent };
diff --git a/Tests/Runtime/Agents/TimeBombAgentTest.cs b/Tests/Runtime/Agents/TimeBombAgentTest.cs
index b4b749f..07b1379 100644
--- a/Tests/Runtime/Agents/TimeBombAgentTest.cs
+++ b/Tests/Runtime/Agents/TimeBombAgentTest.cs
@@ -42,7 +42,7 @@ public async Task Run_CancelTask_StopAgent()
{
var monkeyAgent = CreateMonkeyAgent(5);
var agent = CreateTimeBombAgent(monkeyAgent, "^Never match!$");
- agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ agent.Logger = Debug.unityLogger;
agent.Random = new RandomFactory(0).CreateRandom();
using (var cts = new CancellationTokenSource())
@@ -68,7 +68,7 @@ public async Task Run_Defuse_StopAgent()
{
var monkeyAgent = CreateMonkeyAgent(5);
var agent = CreateTimeBombAgent(monkeyAgent, "^Tutorial Completed!$");
- agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ agent.Logger = Debug.unityLogger;
agent.Random = new RandomFactory(0).CreateRandom();
using (var cts = new CancellationTokenSource())
@@ -89,7 +89,7 @@ public async Task Run_NotDefuse_ThrowsTimeoutException()
{
var monkeyAgent = CreateMonkeyAgent(1);
var agent = CreateTimeBombAgent(monkeyAgent, "^Never match!$");
- agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ agent.Logger = Debug.unityLogger;
agent.Random = new RandomFactory(0).CreateRandom();
using (var cts = new CancellationTokenSource())
diff --git a/Tests/Runtime/Agents/UGUIMonkeyAgentTest.cs b/Tests/Runtime/Agents/UGUIMonkeyAgentTest.cs
index e8cd1ae..a4b1f97 100644
--- a/Tests/Runtime/Agents/UGUIMonkeyAgentTest.cs
+++ b/Tests/Runtime/Agents/UGUIMonkeyAgentTest.cs
@@ -36,7 +36,7 @@ await EditorSceneManager.LoadSceneAsyncInPlayMode(
public async Task Run_cancelTask_stopAgent()
{
var agent = ScriptableObject.CreateInstance();
- agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ agent.Logger = Debug.unityLogger;
agent.Random = new RandomFactory(0).CreateRandom();
agent.name = nameof(Run_cancelTask_stopAgent);
agent.lifespanSec = 0; // Expect indefinite execution
@@ -60,7 +60,7 @@ public async Task Run_cancelTask_stopAgent()
public async Task Run_lifespanPassed_stopAgent()
{
var agent = ScriptableObject.CreateInstance();
- agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ agent.Logger = Debug.unityLogger;
agent.Random = new RandomFactory(0).CreateRandom();
agent.name = nameof(Run_lifespanPassed_stopAgent);
agent.lifespanSec = 1;
@@ -94,7 +94,7 @@ public async Task Run_DefaultScreenshotFilenamePrefix_UseAgentName()
Assume.That(path, Does.Not.Exist);
var agent = ScriptableObject.CreateInstance();
- agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ agent.Logger = Debug.unityLogger;
agent.Random = new RandomFactory(0).CreateRandom();
agent.name = AgentName;
agent.lifespanSec = 1;
diff --git a/Tests/Runtime/Agents/UGUIPlaybackAgentTest.cs b/Tests/Runtime/Agents/UGUIPlaybackAgentTest.cs
index 104a709..467c3ed 100644
--- a/Tests/Runtime/Agents/UGUIPlaybackAgentTest.cs
+++ b/Tests/Runtime/Agents/UGUIPlaybackAgentTest.cs
@@ -30,7 +30,7 @@ await EditorSceneManager.LoadSceneAsyncInPlayMode(
public async Task Run_cancelTask_stopAgent()
{
var agent = ScriptableObject.CreateInstance();
- agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ agent.Logger = Debug.unityLogger;
agent.Random = new RandomFactory(0).CreateRandom();
agent.name = nameof(Run_cancelTask_stopAgent);
agent.recordedJson = AssetDatabase.LoadAssetAtPath(
@@ -54,7 +54,7 @@ public async Task Run_cancelTask_stopAgent()
public async Task Run_playbackFinished_stopAgent()
{
var agent = ScriptableObject.CreateInstance();
- agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler);
+ agent.Logger = Debug.unityLogger;
agent.Random = new RandomFactory(0).CreateRandom();
agent.name = nameof(Run_playbackFinished_stopAgent);
agent.recordedJson = AssetDatabase.LoadAssetAtPath(
diff --git a/Tests/Runtime/AutopilotTest.cs b/Tests/Runtime/AutopilotTest.cs
new file mode 100644
index 0000000..cd87fa0
--- /dev/null
+++ b/Tests/Runtime/AutopilotTest.cs
@@ -0,0 +1,44 @@
+// Copyright (c) 2023-2024 DeNA Co., Ltd.
+// This software is released under the MIT License.
+
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using DeNA.Anjin.Settings;
+using DeNA.Anjin.TestDoubles;
+using NUnit.Framework;
+using UnityEngine;
+using UnityEngine.TestTools;
+
+namespace DeNA.Anjin
+{
+ [TestFixture]
+ public class AutopilotTest
+ {
+ [Test]
+ public async Task Start_LoggerIsNotSet_UsingDefaultLogger()
+ {
+ var autopilotSettings = ScriptableObject.CreateInstance();
+ autopilotSettings.sceneAgentMaps = new List();
+ autopilotSettings.lifespanSec = 1;
+
+ await LauncherFromTest.AutopilotAsync(autopilotSettings);
+
+ LogAssert.Expect(LogType.Log, "Launched autopilot"); // using console logger
+ }
+
+ [Test]
+ public async Task Start_LoggerSpecified_UsingSpecifiedLogger()
+ {
+ var autopilotSettings = ScriptableObject.CreateInstance();
+ autopilotSettings.sceneAgentMaps = new List();
+ autopilotSettings.lifespanSec = 1;
+ var spyLogger = ScriptableObject.CreateInstance();
+ autopilotSettings.loggerAsset = spyLogger;
+
+ await LauncherFromTest.AutopilotAsync(autopilotSettings);
+
+ Assert.That(spyLogger.Logs, Does.Contain("Launched autopilot")); // using spy logger
+ LogAssert.NoUnexpectedReceived(); // not write to console
+ }
+ }
+}
diff --git a/Tests/Runtime/AutopilotTest.cs.meta b/Tests/Runtime/AutopilotTest.cs.meta
new file mode 100644
index 0000000..4244c38
--- /dev/null
+++ b/Tests/Runtime/AutopilotTest.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: a14a91f9386146a5b97b92704bee4cfc
+timeCreated: 1714869131
\ No newline at end of file
diff --git a/Tests/Runtime/Loggers.meta b/Tests/Runtime/Loggers.meta
new file mode 100644
index 0000000..ab76f99
--- /dev/null
+++ b/Tests/Runtime/Loggers.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 29af5ac6a1de46fdb89fd8495a14e22b
+timeCreated: 1714878353
\ No newline at end of file
diff --git a/Tests/Runtime/Loggers/CompositeLoggerAssetTest.cs b/Tests/Runtime/Loggers/CompositeLoggerAssetTest.cs
new file mode 100644
index 0000000..72d217e
--- /dev/null
+++ b/Tests/Runtime/Loggers/CompositeLoggerAssetTest.cs
@@ -0,0 +1,164 @@
+// Copyright (c) 2023-2024 DeNA Co., Ltd.
+// This software is released under the MIT License.
+
+using System;
+using DeNA.Anjin.TestDoubles;
+using NUnit.Framework;
+using UnityEngine;
+
+namespace DeNA.Anjin.Loggers
+{
+ [TestFixture]
+ public class CompositeLoggerAssetTest
+ {
+ private static Exception CreateExceptionWithStacktrace(string message)
+ {
+ try
+ {
+ throw new ApplicationException(message);
+ }
+ catch (Exception e)
+ {
+ return e;
+ }
+ }
+
+ [Test]
+ public void LogFormat_CompositeMultipleLoggers_OutputBothLoggers()
+ {
+ var logger1 = ScriptableObject.CreateInstance();
+ var logger2 = ScriptableObject.CreateInstance();
+ var sut = ScriptableObject.CreateInstance();
+ sut.loggerAssets.Add(logger1);
+ sut.loggerAssets.Add(logger2);
+
+ var message = TestContext.CurrentContext.Test.Name;
+ sut.Logger.Log(message);
+
+ Assert.That(logger1.Logs, Does.Contain(message));
+ Assert.That(logger2.Logs, Does.Contain(message));
+ }
+
+ [Test]
+ public void LogFormat_LoggersIncludesNull_IgnoreNull()
+ {
+ var logger2 = ScriptableObject.CreateInstance();
+ var sut = ScriptableObject.CreateInstance();
+ sut.loggerAssets.Add(null);
+ sut.loggerAssets.Add(logger2);
+
+ var message = TestContext.CurrentContext.Test.Name;
+ sut.Logger.Log(message);
+
+ Assert.That(logger2.Logs, Does.Contain(message));
+ }
+
+ [Test]
+ public void LogFormat_NestingLogger_IgnoreNested()
+ {
+ var logger2 = ScriptableObject.CreateInstance();
+ var sut = ScriptableObject.CreateInstance();
+ sut.loggerAssets.Add(sut); // nesting
+ sut.loggerAssets.Add(logger2);
+
+ var message = TestContext.CurrentContext.Test.Name;
+ sut.Logger.Log(message);
+
+ Assert.That(logger2.Logs, Does.Contain(message));
+ }
+
+ [Test]
+ public void LogException_CompositeMultipleLoggers_OutputBothLoggers()
+ {
+ var logger1 = ScriptableObject.CreateInstance();
+ var logger2 = ScriptableObject.CreateInstance();
+ var sut = ScriptableObject.CreateInstance();
+ sut.loggerAssets.Add(logger1);
+ sut.loggerAssets.Add(logger2);
+
+ var message = TestContext.CurrentContext.Test.Name;
+ var exception = CreateExceptionWithStacktrace(message);
+ sut.Logger.LogException(exception);
+
+ Assert.That(logger1.Logs, Does.Contain(exception.ToString()));
+ Assert.That(logger2.Logs, Does.Contain(exception.ToString()));
+ }
+
+ [Test]
+ public void LogException_LoggersIncludesNull_IgnoreNull()
+ {
+ var logger2 = ScriptableObject.CreateInstance();
+ var sut = ScriptableObject.CreateInstance();
+ sut.loggerAssets.Add(null);
+ sut.loggerAssets.Add(logger2);
+
+ var message = TestContext.CurrentContext.Test.Name;
+ var exception = CreateExceptionWithStacktrace(message);
+ sut.Logger.LogException(exception);
+
+ Assert.That(logger2.Logs, Does.Contain(exception.ToString()));
+ }
+
+ [Test]
+ public void LogException_NestingLogger_IgnoreNested()
+ {
+ var logger2 = ScriptableObject.CreateInstance();
+ var sut = ScriptableObject.CreateInstance();
+ sut.loggerAssets.Add(sut); // nesting
+ sut.loggerAssets.Add(logger2);
+
+ var message = TestContext.CurrentContext.Test.Name;
+ var exception = CreateExceptionWithStacktrace(message);
+ sut.Logger.LogException(exception);
+
+ Assert.That(logger2.Logs, Does.Contain(exception.ToString()));
+ }
+
+ [Test]
+ public void Dispose_CompositeMultipleLoggers_DisposeAllLoggers()
+ {
+ var logger1 = ScriptableObject.CreateInstance();
+ var logger2 = ScriptableObject.CreateInstance();
+ var sut = ScriptableObject.CreateInstance();
+ sut.loggerAssets.Add(logger1);
+ sut.loggerAssets.Add(logger2);
+
+ var message = TestContext.CurrentContext.Test.Name;
+ sut.Logger.Log(message);
+ sut.Dispose();
+
+ Assert.That(logger1.Disposed, Is.True);
+ Assert.That(logger2.Disposed, Is.True);
+ }
+
+ [Test]
+ public void Dispose_LoggersIncludesNull_IgnoreNull()
+ {
+ var logger2 = ScriptableObject.CreateInstance();
+ var sut = ScriptableObject.CreateInstance();
+ sut.loggerAssets.Add(null);
+ sut.loggerAssets.Add(logger2);
+
+ var message = TestContext.CurrentContext.Test.Name;
+ sut.Logger.Log(message);
+ sut.Dispose();
+
+ Assert.That(logger2.Disposed, Is.True);
+ }
+
+ [Test]
+ public void Dispose_NestingLogger_IgnoreNested()
+ {
+ var logger2 = ScriptableObject.CreateInstance();
+ var sut = ScriptableObject.CreateInstance();
+ sut.loggerAssets.Add(sut); // nesting
+ sut.loggerAssets.Add(logger2);
+
+ var message = TestContext.CurrentContext.Test.Name;
+ sut.Logger.Log(message);
+ sut.Dispose();
+
+ Assert.That(logger2.Disposed, Is.True);
+ }
+ }
+}
diff --git a/Tests/Runtime/Loggers/CompositeLoggerAssetTest.cs.meta b/Tests/Runtime/Loggers/CompositeLoggerAssetTest.cs.meta
new file mode 100644
index 0000000..de358d1
--- /dev/null
+++ b/Tests/Runtime/Loggers/CompositeLoggerAssetTest.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 63cb26cf1f0441628dcb883967d1911f
+timeCreated: 1714881859
\ No newline at end of file
diff --git a/Tests/Runtime/Loggers/ConsoleLoggerAssetTest.cs b/Tests/Runtime/Loggers/ConsoleLoggerAssetTest.cs
new file mode 100644
index 0000000..f29687b
--- /dev/null
+++ b/Tests/Runtime/Loggers/ConsoleLoggerAssetTest.cs
@@ -0,0 +1,41 @@
+// Copyright (c) 2023-2024 DeNA Co., Ltd.
+// This software is released under the MIT License.
+
+using NUnit.Framework;
+using UnityEngine;
+using UnityEngine.TestTools;
+
+namespace DeNA.Anjin.Loggers
+{
+ [TestFixture]
+ public class ConsoleLoggerAssetTest
+ {
+ [Test]
+ public void FilterLogTypeIsNotSet_LogAsDefault()
+ {
+ var message = TestContext.CurrentContext.Test.Name;
+ var sut = ScriptableObject.CreateInstance();
+
+ sut.Logger.Log(message);
+
+ LogAssert.Expect(LogType.Log, message);
+ LogAssert.NoUnexpectedReceived();
+ }
+
+ [Test]
+ public void FilterLogTypeSpecified(
+ [Values(LogType.Error, LogType.Assert, LogType.Warning, LogType.Exception)]
+ LogType logType)
+ {
+ var message = TestContext.CurrentContext.Test.Name;
+ var sut = ScriptableObject.CreateInstance();
+ sut.filterLogType = logType;
+
+ sut.Logger.Log(logType, message);
+ sut.Logger.Log("Not output message because LogType.Log");
+
+ LogAssert.Expect(logType, message);
+ LogAssert.NoUnexpectedReceived();
+ }
+ }
+}
diff --git a/Tests/Runtime/Loggers/ConsoleLoggerAssetTest.cs.meta b/Tests/Runtime/Loggers/ConsoleLoggerAssetTest.cs.meta
new file mode 100644
index 0000000..0c72e66
--- /dev/null
+++ b/Tests/Runtime/Loggers/ConsoleLoggerAssetTest.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 0a237e9591494b6b92a3b8440dcec4d3
+timeCreated: 1714878397
\ No newline at end of file
diff --git a/Tests/Runtime/Loggers/FileLoggerAssetTest.cs b/Tests/Runtime/Loggers/FileLoggerAssetTest.cs
new file mode 100644
index 0000000..9f44b0e
--- /dev/null
+++ b/Tests/Runtime/Loggers/FileLoggerAssetTest.cs
@@ -0,0 +1,200 @@
+// Copyright (c) 2023-2024 DeNA Co., Ltd.
+// This software is released under the MIT License.
+
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Cysharp.Threading.Tasks;
+using NUnit.Framework;
+using UnityEngine;
+
+// ReSharper disable MethodHasAsyncOverload
+
+namespace DeNA.Anjin.Loggers
+{
+ [TestFixture]
+ public class FileLoggerAssetTest
+ {
+ private const string LogsDirectoryPath = "Logs/FileLoggerTest";
+ private const string TimestampRegex = @"\[\d{2}:\d{2}:\d{2}\.\d{3}\] ";
+
+ private static string GetOutputPath()
+ {
+ return Path.Combine(LogsDirectoryPath, $"{TestContext.CurrentContext.Test.Name}.log");
+ }
+
+ private static Exception CreateExceptionWithStacktrace(string message)
+ {
+ try
+ {
+ throw new ApplicationException(message);
+ }
+ catch (Exception e)
+ {
+ return e;
+ }
+ }
+
+ [Test, Order(0)]
+ public async Task GetLoggerImpl_DirectoryDoesNotExist_CreateDirectoryAndWriteToFile()
+ {
+ if (Directory.Exists(LogsDirectoryPath))
+ {
+ Directory.Delete(LogsDirectoryPath, true);
+ }
+
+ var message = TestContext.CurrentContext.Test.Name;
+ var path = GetOutputPath();
+ var sut = ScriptableObject.CreateInstance();
+ sut.outputPath = path;
+ sut.timestamp = false;
+
+ sut.Logger.Log(message);
+ sut.Dispose();
+ await Task.Yield();
+
+ var actual = File.ReadAllText(path);
+ Assert.That(actual, Is.EqualTo(message + Environment.NewLine));
+ }
+
+ [Test]
+ public async Task GetLoggerImpl_FileExists_OverwrittenToFile()
+ {
+ var message = TestContext.CurrentContext.Test.Name;
+ var path = GetOutputPath();
+ File.WriteAllText(path, "Existing content");
+
+ var sut = ScriptableObject.CreateInstance();
+ sut.outputPath = path;
+ sut.timestamp = false;
+
+ sut.Logger.Log(message);
+ sut.Dispose();
+ await Task.Yield();
+
+ var actual = File.ReadAllText(path);
+ Assert.That(actual, Is.EqualTo(message + Environment.NewLine));
+ }
+
+ [Test]
+ public async Task LogFormat_WriteToFile()
+ {
+ var message = TestContext.CurrentContext.Test.Name;
+ var path = GetOutputPath();
+ var sut = ScriptableObject.CreateInstance();
+ sut.outputPath = path;
+ sut.timestamp = false;
+
+ sut.Logger.Log(message);
+ sut.Logger.Log(message);
+ sut.Dispose();
+ await Task.Yield();
+
+ var actual = File.ReadAllText(path);
+ Assert.That(actual, Is.EqualTo(message + Environment.NewLine + message + Environment.NewLine));
+ }
+
+ [Test]
+ public async Task LogFormat_Disposed_NotWriteToFile()
+ {
+ var message = TestContext.CurrentContext.Test.Name;
+ var path = GetOutputPath();
+ var sut = ScriptableObject.CreateInstance();
+ sut.outputPath = path;
+ sut.timestamp = false;
+
+ sut.Logger.Log(message);
+ sut.Logger.Log(message);
+ sut.Dispose();
+ await Task.Yield();
+
+ sut.Logger.Log(message); // write after disposed
+ await Task.Yield();
+
+ var actual = File.ReadAllText(path);
+ Assert.That(actual, Is.EqualTo(message + Environment.NewLine + message + Environment.NewLine));
+ }
+
+ [Test]
+ public async Task LogFormat_WithTimestamp_WriteTimestamp()
+ {
+ var message = TestContext.CurrentContext.Test.Name;
+ var path = GetOutputPath();
+ var sut = ScriptableObject.CreateInstance();
+ sut.outputPath = path;
+ sut.timestamp = true;
+
+ sut.Logger.Log(message);
+ await UniTask.NextFrame();
+ sut.Logger.Log(message);
+ sut.Logger.Log(message); // using cache
+ sut.Dispose();
+ await Task.Yield();
+
+ var actual = File.ReadAllText(path);
+ Assert.That(actual, Does.Match(
+ "^" +
+ TimestampRegex + message + Environment.NewLine +
+ TimestampRegex + message + Environment.NewLine +
+ TimestampRegex + message + Environment.NewLine +
+ "$"));
+ }
+
+ [Test]
+ public async Task LogException_WriteToFile()
+ {
+ var message = TestContext.CurrentContext.Test.Name;
+ var path = GetOutputPath();
+ var sut = ScriptableObject.CreateInstance();
+ sut.outputPath = path;
+ sut.timestamp = false;
+
+ var exception = CreateExceptionWithStacktrace(message);
+ sut.Logger.LogException(exception);
+ sut.Dispose();
+ await Task.Yield();
+
+ var actual = File.ReadAllText(path);
+ Assert.That(actual, Is.EqualTo(exception + Environment.NewLine));
+ }
+
+ [Test]
+ public async Task LogException_Disposed_NotWriteToFile()
+ {
+ var message = TestContext.CurrentContext.Test.Name;
+ var path = GetOutputPath();
+ var sut = ScriptableObject.CreateInstance();
+ sut.outputPath = path;
+ sut.timestamp = false;
+
+ var exception = CreateExceptionWithStacktrace(message);
+ sut.Logger.LogException(exception);
+ sut.Dispose();
+ await Task.Yield();
+
+ sut.Logger.LogException(exception); // write after disposed
+ await Task.Yield();
+
+ var actual = File.ReadAllText(path);
+ Assert.That(actual, Is.EqualTo(exception + Environment.NewLine));
+ }
+
+ [Test]
+ public async Task LogException_WithTimestamp_WriteTimestamp()
+ {
+ var message = TestContext.CurrentContext.Test.Name;
+ var path = GetOutputPath();
+ var sut = ScriptableObject.CreateInstance();
+ sut.outputPath = path;
+ sut.timestamp = true;
+
+ var exception = CreateExceptionWithStacktrace(message);
+ sut.Logger.LogException(exception);
+ sut.Dispose();
+ await Task.Yield();
+
+ var actual = File.ReadAllText(path);
+ Assert.That(actual, Does.Match("^" + TimestampRegex + ".*"));
+ }
+ }
+}
diff --git a/Tests/Runtime/Loggers/FileLoggerAssetTest.cs.meta b/Tests/Runtime/Loggers/FileLoggerAssetTest.cs.meta
new file mode 100644
index 0000000..525063f
--- /dev/null
+++ b/Tests/Runtime/Loggers/FileLoggerAssetTest.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 4952c4b4da4641dd946e54fcabd98de8
+timeCreated: 1714886802
\ No newline at end of file
diff --git a/Tests/Runtime/TestDoubles/SpyLoggerAsset.cs b/Tests/Runtime/TestDoubles/SpyLoggerAsset.cs
new file mode 100644
index 0000000..6689345
--- /dev/null
+++ b/Tests/Runtime/TestDoubles/SpyLoggerAsset.cs
@@ -0,0 +1,56 @@
+// Copyright (c) 2023-2024 DeNA Co., Ltd.
+// This software is released under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using DeNA.Anjin.Loggers;
+using UnityEngine;
+using Object = UnityEngine.Object;
+
+namespace DeNA.Anjin.TestDoubles
+{
+ public class SpyLoggerAsset : AbstractLoggerAsset
+ {
+ public bool Disposed { get; private set; }
+
+ private SpyLogHandler _handler;
+ private ILogger _logger;
+
+ public override ILogger Logger
+ {
+ get
+ {
+ if (_logger != null)
+ {
+ return _logger;
+ }
+
+ _handler = new SpyLogHandler();
+ _logger = new Logger(_handler);
+ return _logger;
+ }
+ }
+
+ public List Logs => _handler.Logs;
+
+ public override void Dispose()
+ {
+ Disposed = true;
+ }
+
+ private class SpyLogHandler : ILogHandler
+ {
+ public readonly List Logs = new List();
+
+ public void LogFormat(LogType logType, Object context, string format, params object[] args)
+ {
+ Logs.Add(string.Format(format, args));
+ }
+
+ public void LogException(Exception exception, Object context)
+ {
+ Logs.Add(exception.ToString());
+ }
+ }
+ }
+}
diff --git a/Tests/Runtime/TestDoubles/SpyLoggerAsset.cs.meta b/Tests/Runtime/TestDoubles/SpyLoggerAsset.cs.meta
new file mode 100644
index 0000000..2d3ed95
--- /dev/null
+++ b/Tests/Runtime/TestDoubles/SpyLoggerAsset.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: d16166bc06ee4dbdbab913e0ffd48c35
+timeCreated: 1714870143
\ No newline at end of file