#pragma once #include "CoreMinimal.h" #include "Dom/JsonObject.h" // One emission. Call sites fill Name (from TelemetryEvents) and Payload; the subsystem stamps the envelope. struct SALTYCORE_API FTelemetryEvent { FName Name; TSharedPtr Payload; }; // Stamped by the subsystem onto every event. Serialised with snake_case keys; see Telemetry::ToJsonLine. struct SALTYCORE_API FTelemetryEnvelope { int32 SchemaVersion = 1; FGuid EventId; // unique per emission, for de-duplication FDateTime TimestampUtc; float GameTime = 0.f; // seconds since the world began; zero when there is no world yet FGuid SessionId; // minted by the server, adopted by clients on join; invalid before a session FString PlayerId; // hashed server-minted id; empty on the server bool bIsServer = false; // authoritative events versus observed ones int32 PartySize = 0; FString Build; // FApp::GetBuildVersion() plus the git hash FString Map; bool bCheatsUsed = false; // sticky true after the first cheat this session }; // A small fluent builder for payloads, so a call site reads as one expression: // Telemetry->Emit(TelemetryEvents::CheatUsed, FTelemetryPayload().Set(TEXT("command"), Command)); class SALTYCORE_API FTelemetryPayload { public: FTelemetryPayload() : Object(MakeShared()) {} FTelemetryPayload& Set(const FString& Key, const FString& Value) { Object->SetStringField(Key, Value); return *this; } FTelemetryPayload& Set(const FString& Key, const TCHAR* Value) { Object->SetStringField(Key, Value); return *this; } FTelemetryPayload& Set(const FString& Key, FName Value) { Object->SetStringField(Key, Value.ToString()); return *this; } FTelemetryPayload& Set(const FString& Key, const FGuid& Value) { Object->SetStringField(Key, Value.ToString(EGuidFormats::DigitsWithHyphensLower)); return *this; } FTelemetryPayload& Set(const FString& Key, double Value) { Object->SetNumberField(Key, Value); return *this; } FTelemetryPayload& Set(const FString& Key, float Value) { Object->SetNumberField(Key, Value); return *this; } FTelemetryPayload& Set(const FString& Key, int32 Value) { Object->SetNumberField(Key, Value); return *this; } FTelemetryPayload& Set(const FString& Key, int64 Value) { Object->SetNumberField(Key, static_cast(Value)); return *this; } FTelemetryPayload& Set(const FString& Key, bool Value) { Object->SetBoolField(Key, Value); return *this; } TSharedPtr Build() const { return Object; } operator TSharedPtr() const { return Object; } private: TSharedPtr Object; }; namespace Telemetry { // The one serialisation of an event: the envelope's fields as snake_case keys, then "name" and "payload" // (an empty object when the event has none), condensed to a single line with no trailing newline. Pure, so // the sinks share it and the tests can check it without a sink. SALTYCORE_API FString ToJsonLine(const FTelemetryEnvelope& Envelope, const FTelemetryEvent& Event); }