@mflrevan/ucp 0.4.3 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bridge/com.ucp.bridge/CHANGELOG.md +145 -0
- package/bridge/com.ucp.bridge/CHANGELOG.md.meta +7 -0
- package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs +583 -0
- package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Bridge.meta +8 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/AssetController.cs +425 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/AssetController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/AssetImportSupport.cs +355 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/AssetImportSupport.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/BuildController.cs +233 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/BuildController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/CompilationController.cs +26 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/CompilationController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/EditorController.cs +31 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/EditorController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/EditorSettingsController.cs +527 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/EditorSettingsController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/FileController.cs +141 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/FileController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs +326 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ImporterController.cs +209 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ImporterController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/LogsController.cs +409 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/LogsController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs +354 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ObjectReferenceResolver.cs +93 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ObjectReferenceResolver.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PackagesController.cs +503 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PackagesController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PlayModeController.cs +188 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PlayModeController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs +260 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ProfilerController.cs +1679 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ProfilerController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs +563 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneChangeTracker.cs +166 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneChangeTracker.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs +318 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ScreenshotController.cs +125 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ScreenshotController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ScriptController.cs +104 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ScriptController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs +227 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/TestRunnerController.cs +240 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/TestRunnerController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/VcsController.cs +611 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/VcsController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers.meta +8 -0
- package/bridge/com.ucp.bridge/Editor/Protocol/CommandRouter.cs +53 -0
- package/bridge/com.ucp.bridge/Editor/Protocol/CommandRouter.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Protocol/MessageTypes.cs +80 -0
- package/bridge/com.ucp.bridge/Editor/Protocol/MessageTypes.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Protocol/MiniJson.cs +358 -0
- package/bridge/com.ucp.bridge/Editor/Protocol/MiniJson.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Protocol.meta +8 -0
- package/bridge/com.ucp.bridge/Editor/Scripts/IUCPScript.cs +37 -0
- package/bridge/com.ucp.bridge/Editor/Scripts/IUCPScript.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Scripts.meta +8 -0
- package/bridge/com.ucp.bridge/Editor/UCP.Bridge.Editor.asmdef +16 -0
- package/bridge/com.ucp.bridge/Editor/UCP.Bridge.Editor.asmdef.meta +7 -0
- package/bridge/com.ucp.bridge/Editor.meta +8 -0
- package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef +14 -0
- package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef.meta +7 -0
- package/bridge/com.ucp.bridge/Runtime.meta +8 -0
- package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs +1085 -0
- package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Tests/Editor/UCP.Bridge.Editor.Tests.asmdef +12 -0
- package/bridge/com.ucp.bridge/Tests/Editor/UCP.Bridge.Editor.Tests.asmdef.meta +7 -0
- package/bridge/com.ucp.bridge/Tests/Editor.meta +8 -0
- package/bridge/com.ucp.bridge/Tests.meta +8 -0
- package/bridge/com.ucp.bridge/package.json +27 -0
- package/bridge/com.ucp.bridge/package.json.meta +7 -0
- package/package.json +2 -2
- package/scripts/install.js +4 -6
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections.Generic;
|
|
3
|
+
using System.Linq;
|
|
4
|
+
using System.Text.RegularExpressions;
|
|
5
|
+
using UnityEngine;
|
|
6
|
+
|
|
7
|
+
namespace UCP.Bridge
|
|
8
|
+
{
|
|
9
|
+
public static class LogsController
|
|
10
|
+
{
|
|
11
|
+
private const int MaxHistoryEntries = 2000;
|
|
12
|
+
private const int DefaultSearchWindow = 200;
|
|
13
|
+
private const int MaxPreviewLength = 200;
|
|
14
|
+
|
|
15
|
+
private static readonly object s_historyLock = new object();
|
|
16
|
+
private static readonly List<LogRecord> s_history = new List<LogRecord>();
|
|
17
|
+
private static long s_nextId = 1;
|
|
18
|
+
|
|
19
|
+
public static void Register(CommandRouter router)
|
|
20
|
+
{
|
|
21
|
+
router.Register("logs/subscribe", _ => new Dictionary<string, object> { ["subscribed"] = true });
|
|
22
|
+
router.Register("logs/unsubscribe", _ => new Dictionary<string, object> { ["unsubscribed"] = true });
|
|
23
|
+
router.Register("logs/tail", HandleTail);
|
|
24
|
+
router.Register("logs/search", HandleSearch);
|
|
25
|
+
router.Register("logs/get", HandleGet);
|
|
26
|
+
router.Register("logs/status", HandleStatus);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public static Dictionary<string, object> RecordLog(string message, string stackTrace, LogType type)
|
|
30
|
+
{
|
|
31
|
+
return RecordLog(NormalizeLevel(type), message, stackTrace);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public static void ClearHistoryForTests()
|
|
35
|
+
{
|
|
36
|
+
lock (s_historyLock)
|
|
37
|
+
{
|
|
38
|
+
s_history.Clear();
|
|
39
|
+
s_nextId = 1;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public static Dictionary<string, object> RecordTestLog(string level, string message, string stackTrace = "")
|
|
44
|
+
{
|
|
45
|
+
return RecordLog(level, message, stackTrace);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private static object HandleTail(string paramsJson)
|
|
49
|
+
{
|
|
50
|
+
var query = ParseQuery(paramsJson, includePattern: false);
|
|
51
|
+
return BuildListResult(QueryHistory(query));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private static object HandleSearch(string paramsJson)
|
|
55
|
+
{
|
|
56
|
+
var query = ParseQuery(paramsJson, includePattern: true);
|
|
57
|
+
return BuildListResult(QueryHistory(query));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private static object HandleGet(string paramsJson)
|
|
61
|
+
{
|
|
62
|
+
var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
|
|
63
|
+
if (p == null || !p.TryGetValue("id", out var idObj))
|
|
64
|
+
throw new ArgumentException("Missing 'id' parameter");
|
|
65
|
+
|
|
66
|
+
long id = Convert.ToInt64(idObj);
|
|
67
|
+
|
|
68
|
+
lock (s_historyLock)
|
|
69
|
+
{
|
|
70
|
+
var entry = s_history.FirstOrDefault(record => record.Id == id);
|
|
71
|
+
if (entry == null)
|
|
72
|
+
throw new ArgumentException($"Log entry not found: {id}");
|
|
73
|
+
|
|
74
|
+
return SerializeFull(entry);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private static object HandleStatus(string paramsJson)
|
|
79
|
+
{
|
|
80
|
+
lock (s_historyLock)
|
|
81
|
+
{
|
|
82
|
+
var ordered = s_history.OrderBy(entry => entry.Id).ToList();
|
|
83
|
+
var byLevel = new Dictionary<string, object>
|
|
84
|
+
{
|
|
85
|
+
["info"] = ordered.Count(entry => entry.Level == "info"),
|
|
86
|
+
["warning"] = ordered.Count(entry => entry.Level == "warning"),
|
|
87
|
+
["error"] = ordered.Count(entry => entry.Level == "error"),
|
|
88
|
+
["exception"] = ordered.Count(entry => entry.Level == "exception")
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
var grouped = ordered
|
|
92
|
+
.GroupBy(entry => $"{entry.Level}|{Fingerprint(entry.Message)}")
|
|
93
|
+
.Select(group =>
|
|
94
|
+
{
|
|
95
|
+
var first = group.First();
|
|
96
|
+
var last = group.Last();
|
|
97
|
+
return new Dictionary<string, object>
|
|
98
|
+
{
|
|
99
|
+
["level"] = first.Level,
|
|
100
|
+
["fingerprint"] = Fingerprint(first.Message),
|
|
101
|
+
["sampleMessage"] = Preview(first.Message, MaxPreviewLength),
|
|
102
|
+
["count"] = group.Count(),
|
|
103
|
+
["firstTimestamp"] = first.Timestamp,
|
|
104
|
+
["lastTimestamp"] = last.Timestamp,
|
|
105
|
+
["latestId"] = last.Id
|
|
106
|
+
};
|
|
107
|
+
})
|
|
108
|
+
.OrderByDescending(entry => Convert.ToInt32(entry["count"]))
|
|
109
|
+
.ThenBy(entry => entry["sampleMessage"].ToString())
|
|
110
|
+
.ToList();
|
|
111
|
+
|
|
112
|
+
var result = new Dictionary<string, object>
|
|
113
|
+
{
|
|
114
|
+
["total"] = ordered.Count,
|
|
115
|
+
["byLevel"] = byLevel,
|
|
116
|
+
["uniqueCount"] = grouped.Count,
|
|
117
|
+
["topCategories"] = grouped.Take(8).Cast<object>().ToList()
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
if (ordered.Count > 0)
|
|
121
|
+
{
|
|
122
|
+
var first = ordered.First();
|
|
123
|
+
var last = ordered.Last();
|
|
124
|
+
result["firstTimestamp"] = first.Timestamp;
|
|
125
|
+
result["lastTimestamp"] = last.Timestamp;
|
|
126
|
+
result["historyWindowSeconds"] = Math.Max(0d, (last.TimestampUtc - first.TimestampUtc).TotalSeconds);
|
|
127
|
+
result["latestId"] = last.Id;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
var playSession = PlayModeController.GetSessionSnapshot();
|
|
131
|
+
result["play"] = SerializePlaySession(playSession);
|
|
132
|
+
|
|
133
|
+
if (playSession.LastEnteredPlayAtUtc.HasValue)
|
|
134
|
+
{
|
|
135
|
+
var sessionEnd = playSession.Playing
|
|
136
|
+
? DateTime.UtcNow
|
|
137
|
+
: (playSession.LastExitedPlayAtUtc ?? DateTime.UtcNow);
|
|
138
|
+
var sessionLogs = ordered
|
|
139
|
+
.Where(entry => entry.TimestampUtc >= playSession.LastEnteredPlayAtUtc.Value
|
|
140
|
+
&& entry.TimestampUtc <= sessionEnd)
|
|
141
|
+
.ToList();
|
|
142
|
+
|
|
143
|
+
result["lastPlayWindow"] = new Dictionary<string, object>
|
|
144
|
+
{
|
|
145
|
+
["startedAt"] = playSession.LastEnteredPlayAtUtc.Value.ToString("o"),
|
|
146
|
+
["endedAt"] = sessionEnd.ToString("o"),
|
|
147
|
+
["durationSeconds"] = Math.Max(0d, (sessionEnd - playSession.LastEnteredPlayAtUtc.Value).TotalSeconds),
|
|
148
|
+
["total"] = sessionLogs.Count,
|
|
149
|
+
["warnings"] = sessionLogs.Count(entry => entry.Level == "warning"),
|
|
150
|
+
["errors"] = sessionLogs.Count(entry => entry.Level == "error" || entry.Level == "exception")
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private static Dictionary<string, object> RecordLog(string level, string message, string stackTrace)
|
|
159
|
+
{
|
|
160
|
+
lock (s_historyLock)
|
|
161
|
+
{
|
|
162
|
+
var entry = new LogRecord
|
|
163
|
+
{
|
|
164
|
+
Id = s_nextId++,
|
|
165
|
+
Level = NormalizeLevel(level),
|
|
166
|
+
Message = message ?? string.Empty,
|
|
167
|
+
StackTrace = stackTrace ?? string.Empty,
|
|
168
|
+
TimestampUtc = DateTime.UtcNow
|
|
169
|
+
};
|
|
170
|
+
entry.Timestamp = entry.TimestampUtc.ToString("o");
|
|
171
|
+
|
|
172
|
+
s_history.Add(entry);
|
|
173
|
+
if (s_history.Count > MaxHistoryEntries)
|
|
174
|
+
s_history.RemoveAt(0);
|
|
175
|
+
|
|
176
|
+
return SerializeFull(entry);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private static LogQuery ParseQuery(string paramsJson, bool includePattern)
|
|
181
|
+
{
|
|
182
|
+
var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
|
|
183
|
+
var query = new LogQuery();
|
|
184
|
+
|
|
185
|
+
if (p != null)
|
|
186
|
+
{
|
|
187
|
+
if (p.TryGetValue("level", out var levelObj) && levelObj != null)
|
|
188
|
+
query.Level = NormalizeLevel(levelObj.ToString());
|
|
189
|
+
if (p.TryGetValue("count", out var countObj) && countObj != null)
|
|
190
|
+
query.Count = Math.Max(1, Convert.ToInt32(countObj));
|
|
191
|
+
if (p.TryGetValue("beforeId", out var beforeObj) && beforeObj != null)
|
|
192
|
+
query.BeforeId = Convert.ToInt64(beforeObj);
|
|
193
|
+
if (p.TryGetValue("afterId", out var afterObj) && afterObj != null)
|
|
194
|
+
query.AfterId = Convert.ToInt64(afterObj);
|
|
195
|
+
|
|
196
|
+
if (includePattern && p.TryGetValue("pattern", out var patternObj) && patternObj != null)
|
|
197
|
+
{
|
|
198
|
+
query.Pattern = patternObj.ToString();
|
|
199
|
+
try
|
|
200
|
+
{
|
|
201
|
+
query.Regex = new Regex(query.Pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
|
202
|
+
}
|
|
203
|
+
catch (Exception ex)
|
|
204
|
+
{
|
|
205
|
+
throw new ArgumentException($"Invalid regex pattern: {ex.Message}");
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (query.Count <= 0)
|
|
211
|
+
query.Count = DefaultSearchWindow;
|
|
212
|
+
|
|
213
|
+
return query;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private static LogQueryResult QueryHistory(LogQuery query)
|
|
217
|
+
{
|
|
218
|
+
lock (s_historyLock)
|
|
219
|
+
{
|
|
220
|
+
IEnumerable<LogRecord> candidates = s_history;
|
|
221
|
+
|
|
222
|
+
if (query.BeforeId.HasValue)
|
|
223
|
+
candidates = candidates.Where(entry => entry.Id < query.BeforeId.Value);
|
|
224
|
+
if (query.AfterId.HasValue)
|
|
225
|
+
candidates = candidates.Where(entry => entry.Id > query.AfterId.Value);
|
|
226
|
+
if (!string.IsNullOrEmpty(query.Level))
|
|
227
|
+
candidates = candidates.Where(entry => PassesLevel(entry.Level, query.Level));
|
|
228
|
+
|
|
229
|
+
if (query.Regex != null)
|
|
230
|
+
{
|
|
231
|
+
candidates = candidates.Where(entry =>
|
|
232
|
+
query.Regex.IsMatch(entry.Message)
|
|
233
|
+
|| (!string.IsNullOrEmpty(entry.StackTrace) && query.Regex.IsMatch(entry.StackTrace))
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
var allMatches = candidates.OrderByDescending(entry => entry.Id).ToList();
|
|
238
|
+
var returned = allMatches.Take(query.Count).Select(SerializeSummary).ToList();
|
|
239
|
+
|
|
240
|
+
return new LogQueryResult
|
|
241
|
+
{
|
|
242
|
+
Total = allMatches.Count,
|
|
243
|
+
Returned = returned,
|
|
244
|
+
Truncated = allMatches.Count > returned.Count
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private static Dictionary<string, object> BuildListResult(LogQueryResult queryResult)
|
|
250
|
+
{
|
|
251
|
+
return new Dictionary<string, object>
|
|
252
|
+
{
|
|
253
|
+
["logs"] = queryResult.Returned.Cast<object>().ToList(),
|
|
254
|
+
["total"] = queryResult.Total,
|
|
255
|
+
["returned"] = queryResult.Returned.Count,
|
|
256
|
+
["truncated"] = queryResult.Truncated
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private static Dictionary<string, object> SerializeSummary(LogRecord entry)
|
|
261
|
+
{
|
|
262
|
+
return new Dictionary<string, object>
|
|
263
|
+
{
|
|
264
|
+
["id"] = entry.Id,
|
|
265
|
+
["level"] = entry.Level,
|
|
266
|
+
["timestamp"] = entry.Timestamp,
|
|
267
|
+
["messagePreview"] = Preview(entry.Message, MaxPreviewLength),
|
|
268
|
+
["hasStackTrace"] = !string.IsNullOrEmpty(entry.StackTrace)
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private static Dictionary<string, object> SerializeFull(LogRecord entry)
|
|
273
|
+
{
|
|
274
|
+
return new Dictionary<string, object>
|
|
275
|
+
{
|
|
276
|
+
["id"] = entry.Id,
|
|
277
|
+
["level"] = entry.Level,
|
|
278
|
+
["timestamp"] = entry.Timestamp,
|
|
279
|
+
["message"] = entry.Message,
|
|
280
|
+
["stackTrace"] = entry.StackTrace
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private static bool PassesLevel(string value, string threshold)
|
|
285
|
+
{
|
|
286
|
+
return Severity(value) >= Severity(threshold);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
private static int Severity(string level)
|
|
290
|
+
{
|
|
291
|
+
switch (NormalizeLevel(level))
|
|
292
|
+
{
|
|
293
|
+
case "error":
|
|
294
|
+
case "exception":
|
|
295
|
+
return 2;
|
|
296
|
+
case "warning":
|
|
297
|
+
return 1;
|
|
298
|
+
default:
|
|
299
|
+
return 0;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private static string NormalizeLevel(LogType type)
|
|
304
|
+
{
|
|
305
|
+
switch (type)
|
|
306
|
+
{
|
|
307
|
+
case LogType.Error:
|
|
308
|
+
case LogType.Assert:
|
|
309
|
+
return "error";
|
|
310
|
+
case LogType.Exception:
|
|
311
|
+
return "exception";
|
|
312
|
+
case LogType.Warning:
|
|
313
|
+
return "warning";
|
|
314
|
+
default:
|
|
315
|
+
return "info";
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
private static string NormalizeLevel(string level)
|
|
320
|
+
{
|
|
321
|
+
if (string.IsNullOrEmpty(level))
|
|
322
|
+
return "info";
|
|
323
|
+
|
|
324
|
+
var normalized = level.Trim().ToLowerInvariant();
|
|
325
|
+
switch (normalized)
|
|
326
|
+
{
|
|
327
|
+
case "warn":
|
|
328
|
+
return "warning";
|
|
329
|
+
case "err":
|
|
330
|
+
return "error";
|
|
331
|
+
default:
|
|
332
|
+
return normalized;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private static string Preview(string value, int maxChars)
|
|
337
|
+
{
|
|
338
|
+
if (string.IsNullOrEmpty(value) || value.Length <= maxChars)
|
|
339
|
+
return value ?? string.Empty;
|
|
340
|
+
|
|
341
|
+
return value.Substring(0, maxChars) + "...";
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private static string Fingerprint(string message)
|
|
345
|
+
{
|
|
346
|
+
var firstLine = (message ?? string.Empty)
|
|
347
|
+
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
|
348
|
+
.FirstOrDefault() ?? string.Empty;
|
|
349
|
+
var normalized = firstLine.Trim();
|
|
350
|
+
normalized = Regex.Replace(normalized, @"0x[0-9a-fA-F]+", "<hex>");
|
|
351
|
+
normalized = Regex.Replace(normalized, @"\b\d+\b", "<n>");
|
|
352
|
+
normalized = Regex.Replace(normalized, @"\s+", " ");
|
|
353
|
+
return normalized;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
private static Dictionary<string, object> SerializePlaySession(PlayModeController.SessionSnapshot snapshot)
|
|
357
|
+
{
|
|
358
|
+
var result = new Dictionary<string, object>
|
|
359
|
+
{
|
|
360
|
+
["playing"] = snapshot.Playing,
|
|
361
|
+
["paused"] = snapshot.Paused,
|
|
362
|
+
["willChange"] = snapshot.WillChange,
|
|
363
|
+
["compiling"] = snapshot.Compiling
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
if (snapshot.LastPlayRequestedAtUtc.HasValue)
|
|
367
|
+
result["lastPlayRequestedAt"] = snapshot.LastPlayRequestedAtUtc.Value.ToString("o");
|
|
368
|
+
if (snapshot.LastEnteredPlayAtUtc.HasValue)
|
|
369
|
+
result["lastEnteredPlayAt"] = snapshot.LastEnteredPlayAtUtc.Value.ToString("o");
|
|
370
|
+
if (snapshot.LastStopRequestedAtUtc.HasValue)
|
|
371
|
+
result["lastStopRequestedAt"] = snapshot.LastStopRequestedAtUtc.Value.ToString("o");
|
|
372
|
+
if (snapshot.LastExitedPlayAtUtc.HasValue)
|
|
373
|
+
result["lastExitedPlayAt"] = snapshot.LastExitedPlayAtUtc.Value.ToString("o");
|
|
374
|
+
if (snapshot.Playing && snapshot.LastEnteredPlayAtUtc.HasValue)
|
|
375
|
+
result["currentPlayDurationSeconds"] = Math.Max(0d, (DateTime.UtcNow - snapshot.LastEnteredPlayAtUtc.Value).TotalSeconds);
|
|
376
|
+
if (snapshot.LastEnteredPlayAtUtc.HasValue && snapshot.LastExitedPlayAtUtc.HasValue)
|
|
377
|
+
result["lastPlayDurationSeconds"] = Math.Max(0d, (snapshot.LastExitedPlayAtUtc.Value - snapshot.LastEnteredPlayAtUtc.Value).TotalSeconds);
|
|
378
|
+
|
|
379
|
+
return result;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
private sealed class LogRecord
|
|
383
|
+
{
|
|
384
|
+
public long Id;
|
|
385
|
+
public string Level;
|
|
386
|
+
public string Message;
|
|
387
|
+
public string StackTrace;
|
|
388
|
+
public string Timestamp;
|
|
389
|
+
public DateTime TimestampUtc;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
private sealed class LogQuery
|
|
393
|
+
{
|
|
394
|
+
public string Level;
|
|
395
|
+
public string Pattern;
|
|
396
|
+
public Regex Regex;
|
|
397
|
+
public int Count;
|
|
398
|
+
public long? BeforeId;
|
|
399
|
+
public long? AfterId;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
private sealed class LogQueryResult
|
|
403
|
+
{
|
|
404
|
+
public int Total;
|
|
405
|
+
public List<Dictionary<string, object>> Returned;
|
|
406
|
+
public bool Truncated;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|