-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathEventLogger.cs
More file actions
122 lines (112 loc) · 4.4 KB
/
EventLogger.cs
File metadata and controls
122 lines (112 loc) · 4.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
using System;
using System.Globalization;
using System.IO;
using System.Text;
namespace ScheduleObserved;
/// <summary>
/// Append-only JSONL event writer for structured deal events. One JSON object per line.
/// Written to <c>UserData/ScheduleObserved/events.jsonl</c> so Promtail can tail it and
/// ship to Loki. No external dependencies (hand-rolled JSON) to avoid pulling
/// Newtonsoft into the Unity runtime.
/// </summary>
internal static class EventLogger
{
private static string? _path;
private static readonly object _lock = new();
public static void Init()
{
try
{
string userDataDir = MelonLoader.Utils.MelonEnvironment.UserDataDirectory;
string dir = Path.Combine(userDataDir, "ScheduleObserved");
Directory.CreateDirectory(dir);
_path = Path.Combine(dir, "events.jsonl");
Plugin.Log.Msg($"EventLogger writing to {_path}");
}
catch (Exception ex)
{
Plugin.Log.Warning($"EventLogger init failed: {ex.Message}");
}
}
/// <summary>
/// Append one deal event. Safe to call from the Unity main thread; lock-protected so
/// off-thread callers don't interleave (not expected in practice, but cheap insurance).
/// </summary>
public static void LogDeal(
string saveSlot,
string customer,
string productId,
string drugType,
double basePayment,
long units,
float x,
float z)
{
if (_path == null) return;
try
{
string line = BuildDealJson(saveSlot, customer, productId, drugType, basePayment, units, x, z);
lock (_lock)
{
File.AppendAllText(_path, line + "\n", Encoding.UTF8);
}
}
catch (Exception ex)
{
Plugin.Log.Warning($"EventLogger.LogDeal failed: {ex.Message}");
}
}
private static string BuildDealJson(
string saveSlot, string customer, string productId, string drugType,
double basePayment, long units, float x, float z)
{
// Map Unity world coords to lat/lon by dividing by 1000. Schedule 1 is nowhere near
// Earth, so points render in the Atlantic off Ghana on an OSM basemap — that's the
// joke. Projects with a custom tile server can skip the /1000 scaling.
double lat = z / 1000.0;
double lon = x / 1000.0;
var sb = new StringBuilder(256);
sb.Append('{');
StringField(sb, "ts", DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture)); sb.Append(',');
StringField(sb, "event", "deal"); sb.Append(',');
StringField(sb, "save_slot", saveSlot); sb.Append(',');
StringField(sb, "customer", customer); sb.Append(',');
StringField(sb, "product_id", productId); sb.Append(',');
StringField(sb, "drug_type", drugType); sb.Append(',');
NumberField(sb, "base", basePayment.ToString("F2", CultureInfo.InvariantCulture)); sb.Append(',');
NumberField(sb, "units", units.ToString(CultureInfo.InvariantCulture)); sb.Append(',');
NumberField(sb, "lat", lat.ToString("F4", CultureInfo.InvariantCulture)); sb.Append(',');
NumberField(sb, "lon", lon.ToString("F4", CultureInfo.InvariantCulture));
sb.Append('}');
return sb.ToString();
}
private static void StringField(StringBuilder sb, string key, string value)
{
sb.Append('"').Append(key).Append("\":\"").Append(EscJson(value)).Append('"');
}
private static void NumberField(StringBuilder sb, string key, string numberLiteral)
{
sb.Append('"').Append(key).Append("\":").Append(numberLiteral);
}
private static string EscJson(string s)
{
if (string.IsNullOrEmpty(s)) return "";
var sb = new StringBuilder(s.Length + 8);
foreach (char c in s)
{
switch (c)
{
case '\\': sb.Append("\\\\"); break;
case '"': sb.Append("\\\""); break;
case '\n': sb.Append("\\n"); break;
case '\r': sb.Append("\\r"); break;
case '\t': sb.Append("\\t"); break;
default:
if (c < 0x20) sb.Append("\\u").Append(((int)c).ToString("x4", CultureInfo.InvariantCulture));
else sb.Append(c);
break;
}
}
return sb.ToString();
}
}