Supports uploading logs to the server.

支持上传日志到服务器。
This commit is contained in:
Cold-Mint 2024-07-15 22:17:43 +08:00
commit f1b696ad5e
Signed by: Cold-Mint
GPG Key ID: C5A9BF8A98E0CE99
23 changed files with 406 additions and 96 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ export_presets.cfg
.vs/ .vs/
*.translation *.translation
*.user *.user
AppConfig.yaml

View File

@ -52,6 +52,34 @@ You need to fill in the Export Presets > Resources > Filter to export non-resour
data/* data/*
``` ```
## Configuring Openobserve
> This is optional, and the game will work even if you do not configure Openobserve.
openobserve is used to continuously collect logs and alarm information after a game has been released.
#### Set up the openobserve server
see[openobserve](https://github.com/openobserve/openobserve)
#### Write configuration
After you have set up an openobserve server, follow the following steps to configure the file:
1. Create a configuration file named **AppConfig.yaml** in the root directory of your project.
2. Fill in the information for the remote server.
```yaml
open_observe:
address: [address]
access_token: [token]
org_id: [org_id]
stream_name: [stream_name]
```
address Indicates the address of the server in the format of http(s)://www.example.com. (Support http and https)
## Participate in translation ## Participate in translation
The project is prepared for localization at the beginning of writing. You can edit the csv file in the locals directory. To modify and add new translations. The project is prepared for localization at the beginning of writing. You can edit the csv file in the locals directory. To modify and add new translations.

View File

@ -50,6 +50,34 @@ git clone https://github.com/Cold-Mint/Traveller.git
data/* data/*
``` ```
## はいちOpenobserve
> これはオプションなので、Openobserveを設定しなくてもゲームは正常に動作します。
openobserveは、リリース後にログやアラームを継続的に収集するために使用されます。
#### 搭建openobserve衣服务器
読み過ごす:[openobserve](https://github.com/openobserve/openobserve)
#### 構成を書きます
openobserveのサーバーを構築したら、次のようにファイルを設定します。
1. プロジェクトのルートディレクトリに**AppConfig.yaml **というプロファイルを作成します。
2. リモートサーバーの情報を入力します。
```yaml
open_observe:
address: [address]
access_token: [token]
org_id: [org_id]
stream_name: [stream_name]
```
address サーバーのアドレス、フォーマットはこうなります http(s)://www.example.com。支持http和https
## 翻訳に携わります ## 翻訳に携わります
このプロジェクトは、当初からローカライズの準備ができていました。localsディレクトリのcsvファイルを編集することができます。新しい翻訳を加えたり修正したりしています このプロジェクトは、当初からローカライズの準備ができていました。localsディレクトリのcsvファイルを編集することができます。新しい翻訳を加えたり修正したりしています

View File

@ -52,6 +52,34 @@ git clone https://github.com/Cold-Mint/Traveller.git
data/* data/*
``` ```
## 配置Openobserve
> 这是可选的操作即使您不配置Openobserve游戏也能正常运行。
openobserve用于在游戏发布后持续收集日志和报警信息。
#### 搭建openobserve服务器
请见:[openobserve](https://github.com/openobserve/openobserve)
#### 编写配置
在您搭建完毕openobserve的服务器后按如下步骤配置文件
1. 在项目的根目录创建名为**AppConfig.yaml**的配置文件。
2. 填入远程服务器的信息。
```yaml
open_observe:
address: [address]
access_token: [token]
org_id: [org_id]
stream_name: [stream_name]
```
address 服务器的地址,格式为 http(s)://www.example.com。支持http和https
## 参与翻译 ## 参与翻译
此项目在编写之初就为本地化做好了准备。您可以编辑locals目录下的csv文件。来修改和添加新的翻译。 此项目在编写之初就为本地化做好了准备。您可以编辑locals目录下的csv文件。来修改和添加新的翻译。

View File

@ -23,7 +23,7 @@ log_player_packed_scene_not_exist,玩家预制场景不存在。,Player packed s
log_exit_the_room_debug,节点{0}退出房间{1}。,"Node {0} exits room {1}.",ノード{0}が部屋{1}を退出します。 log_exit_the_room_debug,节点{0}退出房间{1}。,"Node {0} exits room {1}.",ノード{0}が部屋{1}を退出します。
log_enter_the_room_debug,节点{0}进入房间{1}。,"Node {0} enters room {1}.",ノード{0}が部屋{1}に入ります。 log_enter_the_room_debug,节点{0}进入房间{1}。,"Node {0} enters room {1}.",ノード{0}が部屋{1}に入ります。
log_death_info,生物{0}被{1}击败。,"Creature {0} was defeated by {1}.",生物{0}が{1}によって打ち負かされました。 log_death_info,生物{0}被{1}击败。,"Creature {0} was defeated by {1}.",生物{0}が{1}によって打ち負かされました。
log_appConfig_not_exist,您可以在项目根目录创建名为AppConfig.yaml的文件并在其中配置OpenObserve的数据以便在游戏发布后持续收集日志和运行数据。,You can create a file named AppConfig.yaml in the project root directory and configure OpenObserve data in it to collect log and run data continuously after the game has been released.,プロジェクトのルートディレクトリにappconfig.yamlというファイルを作成し、そこにOpenObserveのデータを配置して、リリース後も継続的にログや実行データを収集することができます。
log_loot_list_has_no_entries,ID为{0}的战利品表,没有指定条目。,"Loot list with ID {0}, no entry specified.",ID{0}の戦利品テーブルは、エントリ指定されていません。 log_loot_list_has_no_entries,ID为{0}的战利品表,没有指定条目。,"Loot list with ID {0}, no entry specified.",ID{0}の戦利品テーブルは、エントリ指定されていません。
log_not_within_the_loot_spawn_range,给定的数值{0}没有在战利品{1}的生成范围{2}内。,The given value {0} is not within the spawn range {2} of loot {1}.,与えられた数値{0}は戦利品{1}の生成範囲{2}内にありません。 log_not_within_the_loot_spawn_range,给定的数值{0}没有在战利品{1}的生成范围{2}内。,The given value {0} is not within the spawn range {2} of loot {1}.,与えられた数値{0}は戦利品{1}の生成範囲{2}内にありません。
log_loot_data_quantity,有{0}个战利品数据被返回。,{0} loot data was returned.,{0}個の戦利品データが返されます。 log_loot_data_quantity,有{0}个战利品数据被返回。,{0} loot data was returned.,{0}個の戦利品データが返されます。
@ -76,3 +76,7 @@ log_weaponContainer_is_null,武器容器为空。,Weapon container is null.,武
log_find_nearest_item,查找最近的物品。,Find the nearest item.,最も近いアイテムを見つけます。 log_find_nearest_item,查找最近的物品。,Find the nearest item.,最も近いアイテムを見つけます。
log_float_label_instantiate_failed,浮动标签实例化失败。,Float label instantiation failed.,フロートラベルのインスタンス化に失敗しました。 log_float_label_instantiate_failed,浮动标签实例化失败。,Float label instantiation failed.,フロートラベルのインスタンス化に失敗しました。
log_pickable_picked_up,可拾捡物被捡起了,那么不显示标签。,"If the pickable item is picked up, the label is not displayed.",でも、拾得物が拾い上げられたら、ラベルは表示されません。 log_pickable_picked_up,可拾捡物被捡起了,那么不显示标签。,"If the pickable item is picked up, the label is not displayed.",でも、拾得物が拾い上げられたら、ラベルは表示されません。
log_start_uploading,开始上传{0}条日志。,Start uploading {0} logs.,{0}個のログをアップロードを開始します。
log_upload_successful,上传成功,已上传{0}条日志,剩余{1}条日志待上传。,"Upload successful, {0} logs uploaded, {1} logs remaining.",アップロードが成功しました、{0}個のログがアップロードされ、{1}個のログが残っています。
log_upload_failed,上传失败,错误代码:{0},剩余{1}条日志待上传。,"Upload failed, error code: {0}, {1} logs remaining.",アップロードに失敗しました、エラーコード:{0}、{1}個のログが残っています。
log_upload_status,已记录{0}条日志,上传阈值为{1}。,"{0} logs recorded, upload threshold is {1}.",{0}個のログが記録され、アップロード閾値は{1}です。
1 id zh en ja
23 log_exit_the_room_debug 节点{0}退出房间{1}。 Node {0} exits room {1}. ノード{0}が部屋{1}を退出します。
24 log_enter_the_room_debug 节点{0}进入房间{1}。 Node {0} enters room {1}. ノード{0}が部屋{1}に入ります。
25 log_death_info 生物{0}被{1}击败。 Creature {0} was defeated by {1}. 生物{0}が{1}によって打ち負かされました。
26 log_loot_list_has_no_entries log_appConfig_not_exist ID为{0}的战利品表,没有指定条目。 您可以在项目根目录创建名为AppConfig.yaml的文件,并在其中配置OpenObserve的数据,以便在游戏发布后持续收集日志和运行数据。 Loot list with ID {0}, no entry specified. You can create a file named AppConfig.yaml in the project root directory and configure OpenObserve data in it to collect log and run data continuously after the game has been released. ID{0}の戦利品テーブルは、エントリ指定されていません。 プロジェクトのルートディレクトリにappconfig.yamlというファイルを作成し、そこにOpenObserveのデータを配置して、リリース後も継続的にログや実行データを収集することができます。
27 log_not_within_the_loot_spawn_range log_loot_list_has_no_entries 给定的数值{0}没有在战利品{1}的生成范围{2}内。 ID为{0}的战利品表,没有指定条目。 The given value {0} is not within the spawn range {2} of loot {1}. Loot list with ID {0}, no entry specified. 与えられた数値{0}は戦利品{1}の生成範囲{2}内にありません。 ID{0}の戦利品テーブルは、エントリ指定されていません。
28 log_loot_data_quantity log_not_within_the_loot_spawn_range 有{0}个战利品数据被返回。 给定的数值{0}没有在战利品{1}的生成范围{2}内。 {0} loot data was returned. The given value {0} is not within the spawn range {2} of loot {1}. {0}個の戦利品データが返されます。 与えられた数値{0}は戦利品{1}の生成範囲{2}内にありません。
29 log_loot_data_add log_loot_data_quantity 生成战利品{0} 有{0}个战利品数据被返回。 Add loot {0} {0} loot data was returned. 戦利品{0}を生成する {0}個の戦利品データが返されます。
76 log_pickable_picked_up 可拾捡物被捡起了,那么不显示标签。 If the pickable item is picked up, the label is not displayed. でも、拾得物が拾い上げられたら、ラベルは表示されません。
77 log_start_uploading 开始上传{0}条日志。 Start uploading {0} logs. {0}個のログをアップロードを開始します。
78 log_upload_successful 上传成功,已上传{0}条日志,剩余{1}条日志待上传。 Upload successful, {0} logs uploaded, {1} logs remaining. アップロードが成功しました、{0}個のログがアップロードされ、{1}個のログが残っています。
79 log_upload_failed 上传失败,错误代码:{0},剩余{1}条日志待上传。 Upload failed, error code: {0}, {1} logs remaining. アップロードに失敗しました、エラーコード:{0}、{1}個のログが残っています。
80 log_upload_status 已记录{0}条日志,上传阈值为{1}。 {0} logs recorded, upload threshold is {1}. {0}個のログが記録され、アップロード閾値は{1}です。
81
82

82
scripts/AppConfig.cs Normal file
View File

@ -0,0 +1,82 @@
using ColdMint.scripts.debug;
using ColdMint.scripts.openObserve;
using ColdMint.scripts.serialization;
using Godot;
namespace ColdMint.scripts;
public class AppConfig
{
/// <summary>
/// <para>Load configuration from file</para>
/// <para>从文件加载配置</para>
/// </summary>
public static AppConfigData? LoadFromFile()
{
var appConfigExists = FileAccess.FileExists(Config.AppConfigPath);
if (!appConfigExists)
{
LogCat.LogWarning("appConfig_not_exist");
return null;
}
var appConfigFileAccess = FileAccess.Open(Config.AppConfigPath, FileAccess.ModeFlags.Read);
var yamlData = appConfigFileAccess.GetAsText();
appConfigFileAccess.Close();
return YamlSerialization.Deserialize<AppConfigData>(yamlData);
}
/// <summary>
/// <para>ApplyAppConfig</para>
/// <para>应用配置</para>
/// </summary>
/// <param name="appConfigData"></param>
public static void ApplyAppConfig(AppConfigData appConfigData)
{
if (appConfigData.OpenObserve != null)
{
LogCollector.UpdateHttpClient(appConfigData.OpenObserve);
}
}
}
public class AppConfigData
{
/// <summary>
/// <para>OpenObserve configuration information</para>
/// <para>OpenObserve的配置信息</para>
/// </summary>
public OpenObserve? OpenObserve { get; set; }
}
/// <summary>
/// <para>OpenObserve Configuration information</para>
/// <para>OpenObserve配置信息</para>
/// </summary>
public class OpenObserve
{
/// <summary>
/// <para>server address</para>
/// <para>服务器地址</para>
/// </summary>
public string? Address { get; set; }
/// <summary>
/// <para>Access Token</para>
/// <para>访问密匙</para>
/// </summary>
public string? AccessToken { get; set; }
/// <summary>
/// <para>Organization ID</para>
/// <para>组织ID</para>
/// </summary>
public string? OrgId { get; set; }
/// <summary>
/// <para>Stream Name</para>
/// <para>流名称</para>
/// </summary>
public string? StreamName { get; set; }
}

View File

@ -85,6 +85,12 @@ public static class Config
public const string Aborigines = "Aborigines"; public const string Aborigines = "Aborigines";
} }
/// <summary>
/// <para>Path of the App configuration file</para>
/// <para>App配置文件路径</para>
/// </summary>
public const string AppConfigPath = "res://AppConfig.yaml";
/// <summary> /// <summary>
/// <para>The percentage of speed reduced after a thrown item hits an enemy</para> /// <para>The percentage of speed reduced after a thrown item hits an enemy</para>
/// <para>抛出的物品击中敌人后减少的速度百分比</para> /// <para>抛出的物品击中敌人后减少的速度百分比</para>

View File

@ -45,7 +45,7 @@ public partial class BubbleMarker : Marker2D
{ {
if (!_bubbleDictionary.TryGetValue(id, out var value)) if (!_bubbleDictionary.TryGetValue(id, out var value))
{ {
LogCat.LogErrorWithFormat("bubble_not_found", LogCat.LogLabel.BubbleMarker, id); LogCat.LogErrorWithFormat("bubble_not_found", LogCat.LogLabel.BubbleMarker, LogCat.UploadFormat,id);
return; return;
} }
@ -60,7 +60,7 @@ public partial class BubbleMarker : Marker2D
{ {
if (!_bubbleDictionary.TryGetValue(id, out var value)) if (!_bubbleDictionary.TryGetValue(id, out var value))
{ {
LogCat.LogErrorWithFormat("bubble_not_found", LogCat.LogLabel.BubbleMarker, id); LogCat.LogErrorWithFormat("bubble_not_found", LogCat.LogLabel.BubbleMarker, LogCat.UploadFormat,id);
return; return;
} }

View File

@ -40,7 +40,7 @@ public static class CampManager
if (camp.Id != Config.CampId.Default) return false; if (camp.Id != Config.CampId.Default) return false;
_defaultCamp = camp; _defaultCamp = camp;
AddCamp(camp); AddCamp(camp);
LogCat.LogWithFormat("set_default_camp", label: LogCat.LogLabel.CampManager, camp.Id); LogCat.LogWithFormat("set_default_camp", label: LogCat.LogLabel.CampManager, LogCat.UploadFormat, camp.Id);
return true; return true;
} }

View File

@ -585,12 +585,12 @@ public partial class CharacterTemplate : CharacterBody2D
if (damageTemplate.Attacker is CharacterTemplate characterTemplate && if (damageTemplate.Attacker is CharacterTemplate characterTemplate &&
!string.IsNullOrEmpty(characterTemplate.CharacterName)) !string.IsNullOrEmpty(characterTemplate.CharacterName))
{ {
LogCat.LogWithFormat("death_info", LogCat.LogLabel.Default, CharacterName, LogCat.LogWithFormat("death_info", LogCat.LogLabel.Default, LogCat.UploadFormat,CharacterName,
characterTemplate.CharacterName); characterTemplate.CharacterName);
} }
else else
{ {
LogCat.LogWithFormat("death_info", LogCat.LogLabel.Default, CharacterName, LogCat.LogWithFormat("death_info", LogCat.LogLabel.Default, LogCat.UploadFormat,CharacterName,
damageTemplate.Attacker.Name); damageTemplate.Attacker.Name);
} }
} }

View File

@ -48,7 +48,7 @@ public partial class Player : CharacterTemplate
{ {
base._Ready(); base._Ready();
CharacterName = TranslationServerUtils.Translate("default_player_name"); CharacterName = TranslationServerUtils.Translate("default_player_name");
LogCat.LogWithFormat("player_spawn_debug", LogCat.LogLabel.Default, ReadOnlyCharacterName, LogCat.LogWithFormat("player_spawn_debug", LogCat.LogLabel.Default, LogCat.UploadFormat,ReadOnlyCharacterName,
GlobalPosition); GlobalPosition);
var floatLabelPackedScene = GD.Load<PackedScene>("res://prefab/ui/FloatLabel.tscn"); var floatLabelPackedScene = GD.Load<PackedScene>("res://prefab/ui/FloatLabel.tscn");
//Initializes the float label. //Initializes the float label.

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using ColdMint.scripts.openObserve;
using ColdMint.scripts.utils; using ColdMint.scripts.utils;
using Godot; using Godot;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -53,7 +54,13 @@ public static class LogCat
/// <para>BubbleMarker</para> /// <para>BubbleMarker</para>
/// <para>气泡标记</para> /// <para>气泡标记</para>
/// </summary> /// </summary>
public static string BubbleMarker = "BubbleMarker"; public const string BubbleMarker = "BubbleMarker";
/// <summary>
/// <para>LogCollector</para>
/// <para>日志收集器</para>
/// </summary>
public const string LogCollector = "LogCollector";
} }
@ -97,6 +104,12 @@ public static class LogCat
set => _minLogLevel = value; set => _minLogLevel = value;
} }
/// <summary>
/// <para>Whether to upload logs that need to be formatted by default</para>
/// <para>是否默认上传需要格式化的日志</para>
/// </summary>
public static bool UploadFormat { get; set; } = true;
private static readonly StringBuilder StringBuilder = new StringBuilder(); private static readonly StringBuilder StringBuilder = new StringBuilder();
/// <summary> /// <summary>
@ -216,7 +229,14 @@ public static class LogCat
/// </param> /// </param>
/// <param name="label"> /// <param name="label">
/// </param> /// </param>
public static void Log(string message, string label = LogLabel.Default) /// <param name="upload">
/// </param>
public static void Log(string message, string label = LogLabel.Default, bool upload = true)
{
PrintLog(InfoLogLevel, HandleMessage(InfoLogLevel, message, label).ToString(), label, upload);
}
private static void PrintLog(int level, string concreteLog, string label, bool upload)
{ {
if (!IsEnabledLogLabel(label)) if (!IsEnabledLogLabel(label))
{ {
@ -228,7 +248,18 @@ public static class LogCat
return; return;
} }
GD.Print(HandleMessage(InfoLogLevel, message, label)); if (LogCollector.CanUploadLog && upload)
{
var logData = new LogData
{
Level = level,
Message = concreteLog,
AppId = "none"
};
LogCollector.Push(logData);
}
GD.Print(concreteLog);
} }
/// <summary> /// <summary>
@ -242,80 +273,34 @@ public static class LogCat
/// <para>这个消息支持本地化输出假设已存在翻译keyHello = 你好传入Hello则会输出你好。</para> /// <para>这个消息支持本地化输出假设已存在翻译keyHello = 你好传入Hello则会输出你好。</para>
/// </param> /// </param>
/// <param name="label"></param> /// <param name="label"></param>
public static void LogError(string message, string label = LogLabel.Default) /// <param name="upload"></param>
public static void LogError(string message, string label = LogLabel.Default, bool upload = true)
{ {
if (!IsEnabledLogLabel(label)) PrintLog(ErrorLogLevel, HandleMessage(ErrorLogLevel, message, label).ToString(), label, upload);
{
return;
}
if (_minLogLevel > ErrorLogLevel)
{
return;
}
GD.PrintErr(HandleMessage(ErrorLogLevel, message, label));
} }
public static void LogWarning(string message, string label = LogLabel.Default) public static void LogWarning(string message, string label = LogLabel.Default, bool upload = true)
{ {
if (!IsEnabledLogLabel(label)) PrintLog(WarningLogLevel, HandleMessage(WarningLogLevel, message, label).ToString(), label, upload);
{
return;
}
if (_minLogLevel > WarningLogLevel)
{
return;
}
GD.Print(HandleMessage(WarningLogLevel, message, label));
} }
public static void LogErrorWithFormat(string message, string label, params object?[] args) public static void LogErrorWithFormat(string message, string label, bool upload, params object?[] args)
{ {
if (!IsEnabledLogLabel(label)) PrintLog(WarningLogLevel, string.Format(HandleMessage(ErrorLogLevel, message, label).ToString(), args), label,
{ upload);
return;
}
if (_minLogLevel > ErrorLogLevel)
{
return;
}
GD.PrintErr(string.Format(HandleMessage(ErrorLogLevel, message, label).ToString(), args));
} }
public static void LogWithFormat(string message, string label, params object?[] args) public static void LogWithFormat(string message, string label, bool upload, params object?[] args)
{ {
if (!IsEnabledLogLabel(label)) PrintLog(InfoLogLevel, string.Format(HandleMessage(InfoLogLevel, message, label).ToString(), args), label,
{ upload);
return;
}
if (_minLogLevel > InfoLogLevel)
{
return;
}
GD.Print(string.Format(HandleMessage(InfoLogLevel, message, label).ToString(), args));
} }
public static void LogWarningWithFormat(string message, string label, params object?[] args) public static void LogWarningWithFormat(string message, bool upload, string label, params object?[] args)
{ {
if (!IsEnabledLogLabel(label)) PrintLog(WarningLogLevel, string.Format(HandleMessage(WarningLogLevel, message, label).ToString(), args), label,
{ upload);
return;
}
if (_minLogLevel > InfoLogLevel)
{
return;
}
GD.Print(string.Format(HandleMessage(WarningLogLevel, message, label).ToString(), args));
} }
/// <summary> /// <summary>
@ -333,6 +318,7 @@ public static class LogCat
//Log an exception here or send it to the server. //Log an exception here or send it to the server.
//请在这里记录异常或将异常发送至服务器。 //请在这里记录异常或将异常发送至服务器。
GD.PrintErr(HandleMessage(ErrorLogLevel, e.Message, label).Append('\n').Append(e.StackTrace)); PrintLog(ErrorLogLevel,
HandleMessage(ErrorLogLevel, e.Message, label).Append('\n').Append(e.StackTrace).ToString(), label, true);
} }
} }

View File

@ -36,7 +36,7 @@ public static class ItemTypeRegister
var error = DirAccess.GetOpenError(); var error = DirAccess.GetOpenError();
if (error is not Error.Ok) if (error is not Error.Ok)
{ {
LogCat.LogError("error_when_open_item_regs_dir",error.ToString()); LogCat.LogError("error_when_open_item_regs_dir", error.ToString());
} }
//找到文件 //找到文件
@ -44,16 +44,16 @@ public static class ItemTypeRegister
var files = itemRegsDir.GetFiles(); var files = itemRegsDir.GetFiles();
if (files == null) if (files == null)
{ {
LogCat.LogWithFormat("found_files", LogCat.LogLabel.Default, 0); LogCat.LogWithFormat("found_files", LogCat.LogLabel.Default, LogCat.UploadFormat, 0);
return; return;
} }
LogCat.LogWithFormat("found_files", LogCat.LogLabel.Default, files.Length); LogCat.LogWithFormat("found_files", LogCat.LogLabel.Default, LogCat.UploadFormat, files.Length);
//将文件解析为项目类型信息 //将文件解析为项目类型信息
//parse files to item type infos //parse files to item type infos
IEnumerable<ItemTypeInfo> typeInfos = IEnumerable<ItemTypeInfo> typeInfos =
files.SelectMany(file => ParseFile($"{itemRegsDirPath}/{file}")).ToList(); files.SelectMany(file => ParseFile($"{itemRegsDirPath}/{file}")).ToList();
LogCat.LogWithFormat("found_item_types", LogCat.LogLabel.Default, typeInfos.Count()); LogCat.LogWithFormat("found_item_types", LogCat.LogLabel.Default, LogCat.UploadFormat, typeInfos.Count());
//遍历类型信息并注册它们。 //遍历类型信息并注册它们。
//traverse type infos and register them. //traverse type infos and register them.
@ -120,7 +120,8 @@ public static class ItemTypeRegister
}, },
icon, typeInfo.MaxStackValue); icon, typeInfo.MaxStackValue);
var succeed = ItemTypeManager.Register(itemType); var succeed = ItemTypeManager.Register(itemType);
LogCat.LogWithFormat("register_item", label: LogCat.LogLabel.Default, itemType.Id, succeed); LogCat.LogWithFormat("register_item", label: LogCat.LogLabel.Default, LogCat.UploadFormat, itemType.Id,
succeed);
} }
//Use for yaml deserialization //Use for yaml deserialization
@ -151,7 +152,8 @@ public static class ItemTypeRegister
var ss = s.Split(','); var ss = s.Split(',');
if (ss.Length != 2) if (ss.Length != 2)
{ {
LogCat.LogErrorWithFormat("wrong_custom_arg", LogCat.LogLabel.Default, "Vector2", s); LogCat.LogErrorWithFormat("wrong_custom_arg", LogCat.LogLabel.Default, LogCat.UploadFormat, "Vector2",
s);
return Vector2.Zero; return Vector2.Zero;
} }

View File

@ -33,7 +33,12 @@ public partial class MainMenuLoader : UiLoaderTemplate
public override void InitializeData() public override void InitializeData()
{ {
LogCat.Init(); AppConfigData? appConfigData = AppConfig.LoadFromFile();
if (appConfigData != null)
{
AppConfig.ApplyAppConfig(appConfigData);
}
if (Config.IsDebug()) if (Config.IsDebug())
{ {
//Set the minimum log level to Info in debug mode.(Print all logs) //Set the minimum log level to Info in debug mode.(Print all logs)

View File

@ -25,7 +25,7 @@ public readonly struct LootList(string id, IList<LootGroup> groups)
{ {
if (Groups is []) if (Groups is [])
{ {
LogCat.LogWithFormat("loot_list_has_no_entries", LogCat.LogLabel.Default, Id); LogCat.LogWithFormat("loot_list_has_no_entries", LogCat.LogLabel.Default, LogCat.UploadFormat, Id);
return []; return [];
} }
@ -41,10 +41,10 @@ public readonly struct LootList(string id, IList<LootGroup> groups)
//我们为每个战利品条目生成一个战利品数据。 //我们为每个战利品条目生成一个战利品数据。
var datum = group.GenerateLootData(); var datum = group.GenerateLootData();
lootDataList.Add(datum); lootDataList.Add(datum);
LogCat.LogWithFormat("loot_data_add", LogCat.LogLabel.Default, datum); LogCat.LogWithFormat("loot_data_add", LogCat.LogLabel.Default, LogCat.UploadFormat,datum);
} }
LogCat.LogWithFormat("loot_data_quantity", LogCat.LogLabel.Default, lootDataList.Count); LogCat.LogWithFormat("loot_data_quantity", LogCat.LogLabel.Default, LogCat.UploadFormat,lootDataList.Count);
return lootDataList.ToArray(); return lootDataList.ToArray();
} }
} }

View File

@ -241,7 +241,7 @@ public static class MapGenerator
//If the room injection processor cannot be found, a print error occurs. //If the room injection processor cannot be found, a print error occurs.
//如果找不到房间注入处理器,那么打印错误。 //如果找不到房间注入处理器,那么打印错误。
LogCat.LogErrorWithFormat("room_injection_processor_does_not_exist", LogCat.LogErrorWithFormat("room_injection_processor_does_not_exist",
LogCat.LogLabel.Default, injectionProcessorData.Id); LogCat.LogLabel.Default, LogCat.UploadFormat,injectionProcessorData.Id);
continue; continue;
} }
@ -278,7 +278,7 @@ public static class MapGenerator
if (roomPlacementData == null) if (roomPlacementData == null)
{ {
LogCat.LogWithFormat("failed_to_calculate_the_room_location", LogCat.LogLabel.Default, LogCat.LogWithFormat("failed_to_calculate_the_room_location", LogCat.LogLabel.Default,
roomNodeData.Id); LogCat.UploadFormat,roomNodeData.Id);
continue; continue;
} }
@ -324,18 +324,18 @@ public static class MapGenerator
if (dictionary.ContainsKey(roomNodeDataId)) if (dictionary.ContainsKey(roomNodeDataId))
{ {
LogCat.LogWithFormat("place_existing_rooms", LogCat.LogLabel.Default, roomNodeDataId); LogCat.LogWithFormat("place_existing_rooms", LogCat.LogLabel.Default, LogCat.UploadFormat,roomNodeDataId);
return false; return false;
} }
if (!await _roomPlacementStrategy.PlaceRoom(_mapRoot, roomPlacementData)) if (!await _roomPlacementStrategy.PlaceRoom(_mapRoot, roomPlacementData))
{ {
LogCat.LogWarningWithFormat("room_placement_failed", LogCat.LogLabel.Default, roomNodeDataId); LogCat.LogWarningWithFormat("room_placement_failed", LogCat.UploadFormat,LogCat.LogLabel.Default, roomNodeDataId);
return false; return false;
} }
dictionary.Add(roomNodeDataId, roomPlacementData.Room); dictionary.Add(roomNodeDataId, roomPlacementData.Room);
LogCat.LogWithFormat("room_placement_information", LogCat.LogLabel.Default, roomNodeDataId, LogCat.LogWithFormat("room_placement_information", LogCat.LogLabel.Default, LogCat.UploadFormat,roomNodeDataId,
roomPlacementData.Position.ToString()); roomPlacementData.Position.ToString());
return true; return true;
} }

View File

@ -36,7 +36,7 @@ public class Room
{ {
if (_rootNode != null) if (_rootNode != null)
{ {
LogCat.LogWithFormat("enter_the_room_debug", LogCat.LogLabel.Default, node.Name, _rootNode.Name); LogCat.LogWithFormat("enter_the_room_debug", LogCat.LogLabel.Default, LogCat.UploadFormat,node.Name, _rootNode.Name);
} }
if (string.IsNullOrEmpty(EnterRoomEventHandlerId)) if (string.IsNullOrEmpty(EnterRoomEventHandlerId))
@ -57,7 +57,7 @@ public class Room
{ {
if (_rootNode != null) if (_rootNode != null)
{ {
LogCat.LogWithFormat("exit_the_room_debug", LogCat.LogLabel.Default, node.Name, _rootNode.Name); LogCat.LogWithFormat("exit_the_room_debug", LogCat.LogLabel.Default, LogCat.UploadFormat,node.Name, _rootNode.Name);
} }
if (string.IsNullOrEmpty(ExitRoomEventHandlerId)) if (string.IsNullOrEmpty(ExitRoomEventHandlerId))

View File

@ -0,0 +1,139 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http.Json;
using System.Threading.Tasks;
using ColdMint.scripts.debug;
using HttpClient = System.Net.Http.HttpClient;
namespace ColdMint.scripts.openObserve;
/// <summary>
/// <para>LogCollector</para>
/// <para>日志收集器</para>
/// </summary>
public static class LogCollector
{
private static readonly List<LogData> LogDataList = [];
/// <summary>
/// <para>Automatic upload threshold</para>
/// <para>自动上传的阈值</para>
/// </summary>
/// <remarks>
///<para>An attempt is made to upload logs when messages reach this number.</para>
///<para>当消息到达此数量后将尝试上传日志。</para>
/// </remarks>
public static int UploadThreshold { get; set; } = 300;
private static bool _lockList = false;
/// <summary>
/// <para>httpClient</para>
/// <para>Http客户</para>
/// </summary>
private static HttpClient? _httpClient;
private static string? _orgId;
private static string? _streamName;
/// <summary>
/// <para>CanUploadLog</para>
/// <para>是否能上传日志</para>
/// </summary>
public static bool CanUploadLog => _httpClient != null;
/// <summary>
/// <para>UpdateHttpClient</para>
/// <para>更新Http客户端</para>
/// </summary>
/// <param name="openObserve"></param>
public static void UpdateHttpClient(OpenObserve openObserve)
{
if (openObserve.Address == null || openObserve.AccessToken == null || openObserve.OrgId == null ||
openObserve.StreamName == null)
{
return;
}
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(openObserve.Address);
//Add a Cookie to the request header
//添加Cookie到请求头
var cookie = new Cookie("auth_tokens",
"{\"access_token\":\"Basic " + openObserve.AccessToken + "\",\"refresh_token\":\"\"}");
httpClient.DefaultRequestHeaders.Add("Cookie", cookie.ToString());
_httpClient = httpClient;
_orgId = openObserve.OrgId;
_streamName = openObserve.StreamName;
}
/// <summary>
/// <para>Push log</para>
/// <para>推送日志</para>
/// </summary>
/// <param name="logRequestBean"></param>
private static async Task PostLog(List<LogData> logRequestBean)
{
if (_httpClient == null)
{
return;
}
_lockList = true;
LogCat.LogWithFormat("start_uploading", label: LogCat.LogLabel.LogCollector, false, logRequestBean.Count);
var httpResponseMessage =
await _httpClient.PostAsJsonAsync("/api/" + _orgId + "/" + _streamName + "/_json", logRequestBean);
_lockList = false;
if (httpResponseMessage.IsSuccessStatusCode)
{
LogDataList.RemoveRange(0, logRequestBean.Count);
LogCat.LogWithFormat("upload_successful", label: LogCat.LogLabel.LogCollector, false,
logRequestBean.Count, LogDataList.Count);
if (LogDataList.Count > UploadThreshold)
{
//After the upload succeeds, if the threshold is still met, continue uploading.
//上传成功后,如果依然满足阈值,那么继续上传。
PostLog(LogDataList.GetRange(0, UploadThreshold));
}
}
else
{
LogCat.LogWithFormat("upload_failed", label: LogCat.LogLabel.LogCollector, false,
httpResponseMessage.StatusCode.ToString(), LogDataList.Count);
}
}
public static void Push(LogData logData)
{
LogDataList.Add(logData);
LogCat.LogWithFormat("upload_status", LogCat.LogLabel.LogCollector, false, LogDataList.Count, UploadThreshold);
if (!_lockList && LogDataList.Count > UploadThreshold)
{
//执行上传
PostLog(LogDataList.GetRange(0, UploadThreshold));
}
}
}
public class LogData
{
/// <summary>
/// <para>The AppId of this application</para>
/// <para>此应用的AppId</para>
/// </summary>
public string? AppId { get; set; }
/// <summary>
/// <para>message</para>
/// <para>消息</para>
/// </summary>
public string? Message { get; set; }
/// <summary>
/// <para>level</para>
/// <para>错误等级</para>
/// </summary>
public int Level { get; set; }
}

View File

@ -33,7 +33,7 @@ public class StateContext
return; return;
} }
LogCat.LogWithFormat("state_change", label: LogCat.LogLabel.StateContext, _currentState, value); LogCat.LogWithFormat("state_change", label: LogCat.LogLabel.StateContext, LogCat.UploadFormat,_currentState, value);
OnStateChange?.Invoke(_currentState, value); OnStateChange?.Invoke(_currentState, value);
_previousState = _currentState; _previousState = _currentState;
_currentState = value; _currentState = value;

View File

@ -69,7 +69,7 @@ public abstract class StateMachineTemplate : IStateMachine
else else
{ {
LogCat.LogErrorWithFormat("state_processor_not_found", label: LogCat.LogLabel.StateMachineTemplate, LogCat.LogErrorWithFormat("state_processor_not_found", label: LogCat.LogLabel.StateMachineTemplate,
newState); LogCat.UploadFormat,newState);
} }
} }

View File

@ -78,7 +78,7 @@ public class PatrolStateProcessor : StateProcessorTemplate
_originPosition = aiCharacter.GlobalPosition; _originPosition = aiCharacter.GlobalPosition;
LogCat.LogWithFormat("patrol_origin_position", LogCat.LogLabel.PatrolStateProcessor, LogCat.LogWithFormat("patrol_origin_position", LogCat.LogLabel.PatrolStateProcessor,
_originPosition); LogCat.UploadFormat,_originPosition);
} }
var point = _originPosition + Points[_index]; var point = _originPosition + Points[_index];
@ -87,7 +87,7 @@ public class PatrolStateProcessor : StateProcessorTemplate
{ {
//No need to actually come to the patrol point, we just need a distance to get close. //No need to actually come to the patrol point, we just need a distance to get close.
//无需真正的来到巡逻点,我们只需要一个距离接近了就可以了。 //无需真正的来到巡逻点,我们只需要一个距离接近了就可以了。
LogCat.LogWithFormat("patrol_arrival_point", LogCat.LogLabel.PatrolStateProcessor, point); LogCat.LogWithFormat("patrol_arrival_point", LogCat.LogLabel.PatrolStateProcessor, LogCat.UploadFormat,point);
_index++; _index++;
if (_index >= Points.Length) if (_index >= Points.Length)
{ {
@ -96,7 +96,7 @@ public class PatrolStateProcessor : StateProcessorTemplate
} }
else else
{ {
LogCat.LogWithFormat("patrol_to_next_point", label: LogCat.LogLabel.PatrolStateProcessor, point, LogCat.LogWithFormat("patrol_to_next_point", label: LogCat.LogLabel.PatrolStateProcessor, LogCat.UploadFormat,point,
aiCharacter.GlobalPosition, Points[_index], aiCharacter.GlobalPosition, Points[_index],
distance); distance);
aiCharacter.SetTargetPosition(point.Value); aiCharacter.SetTargetPosition(point.Value);

View File

@ -279,7 +279,8 @@ public static class NodeUtils
if (node is T result) return result; if (node is T result) return result;
// If the transformation fails, release the created node // If the transformation fails, release the created node
//如果转型失败,释放所创建的节点 //如果转型失败,释放所创建的节点
LogCat.LogWarningWithFormat("warning_node_cannot_cast_to", LogCat.LogLabel.Default, node, nameof(T)); LogCat.LogWarningWithFormat("warning_node_cannot_cast_to", LogCat.UploadFormat, LogCat.LogLabel.Default, node,
nameof(T));
node.QueueFree(); node.QueueFree();
return null; return null;
} }

View File

@ -33,7 +33,7 @@ public static class TimeUtils
var compNum1 = DateTime.Compare(dateTime, dtStartTime); var compNum1 = DateTime.Compare(dateTime, dtStartTime);
var compNum2 = DateTime.Compare(dateTime, dtEndTime); var compNum2 = DateTime.Compare(dateTime, dtEndTime);
var result = compNum1 >= 0 && compNum2 <= 0; var result = compNum1 >= 0 && compNum2 <= 0;
LogCat.LogWithFormat("time_range_debug", LogCat.LogLabel.Default, dateTime, dtStartTime, dtEndTime, LogCat.LogWithFormat("time_range_debug", LogCat.LogLabel.Default, LogCat.UploadFormat,dateTime, dtStartTime, dtEndTime,
result); result);
return result; return result;
} }