Instrumentation API Reference¶
Complete reference for the Micromegas Unreal Engine instrumentation API.
Header File¶
All instrumentation macros are available by including:
Logging API¶
Choosing between UE_LOG and MICROMEGAS_LOG¶
UE_LOG— convenient, zero code changes. All existing statements are captured automatically by the log interop. Use this by default for low-frequency entries.MICROMEGAS_LOG— prefer this on hot paths.UE_LOGcarries significant per-call overhead (category checks, formatting, printing to the Unreal log sinks) thatMICROMEGAS_LOGavoids — it only pushes the entry onto the telemetry stream. Also useful when you want to log under a custom target without adding aDECLARE_LOG_CATEGORY_EXTERNor polluting the Unreal log.MICROMEGAS_LOG_PROPERTIES— same asMICROMEGAS_LOGbut attaches a set of tag properties to the entry for filtering/grouping in analytics.
MICROMEGAS_LOG¶
Records a log entry under a custom target, bypassing Unreal's log pipeline.
Parameters:
target(const char*): Log target/category (e.g., "Game", "Network", "AI")level(MicromegasTracing::LogLevel): Severity levelmessage(FString): The log message
Log Levels:
MicromegasTracing::LogLevel::Fatal- Critical errors causing shutdownMicromegasTracing::LogLevel::Error- Errors requiring attentionMicromegasTracing::LogLevel::Warn- Warning conditionsMicromegasTracing::LogLevel::Info- Informational messagesMicromegasTracing::LogLevel::Debug- Debug informationMicromegasTracing::LogLevel::Trace- Detailed trace information
Example:
MICROMEGAS_LOG("Game", MicromegasTracing::LogLevel::Info,
TEXT("Player connected"));
MICROMEGAS_LOG("Network", MicromegasTracing::LogLevel::Error,
FString::Printf(TEXT("Connection failed: %s"), *ErrorMessage));
MICROMEGAS_LOG_PROPERTIES¶
Records a log entry with additional structured properties.
Parameters:
target(const char*): Log target/categorylevel(MicromegasTracing::LogLevel): Severity levelproperties(PropertySet*): Additional key-value propertiesmessage(FString): The log message
PropertySet instances are interned by Dispatch::GetPropertySet — the same context map always returns the same pointer, so repeated calls with the same tags are cheap and safe to keep alive.
Example:
TMap<FName, FName> Context;
Context.Add(TEXT("player_id"), TEXT("12345"));
Context.Add(TEXT("action"), TEXT("login"));
const MicromegasTracing::PropertySet* Props =
MicromegasTracing::Dispatch::GetPropertySet(Context);
MICROMEGAS_LOG_PROPERTIES("Game", MicromegasTracing::LogLevel::Info,
Props, TEXT("Player action recorded"));
UE_LOG Integration¶
All existing UE_LOG statements are automatically captured by Micromegas when the log interop is initialized. No code changes required.
// These are automatically sent to telemetry
UE_LOG(LogTemp, Warning, TEXT("This is captured by Micromegas"));
UE_LOG(LogGameMode, Error, TEXT("So is this"));
Metrics API¶
MICROMEGAS_IMETRIC¶
Records an integer metric value.
Parameters:
target(const char*): Metric target/categorylevel(MicromegasTracing::Verbosity): Verbosity levelname(const TCHAR*): Metric nameunit(const TCHAR*): Unit of measurementexpression(int64): Value or expression to record
Verbosity Levels:
MicromegasTracing::Verbosity::Min- Low-frequency / critical metricsMicromegasTracing::Verbosity::Med- Frame-frequency metricsMicromegasTracing::Verbosity::Max- Many instances per frame
Common Units:
TEXT("count")- Simple counterTEXT("bytes")- Memory/data sizeTEXT("ms")- MillisecondsTEXT("percent")- Percentage (0-100)TEXT("ticks")- Will be automatically converted into nanoseconds
Example:
MICROMEGAS_IMETRIC("Game", MicromegasTracing::Verbosity::Med,
TEXT("PlayerCount"), TEXT("count"),
GetWorld()->GetNumPlayerControllers());
MICROMEGAS_IMETRIC("Memory", MicromegasTracing::Verbosity::Min,
TEXT("TextureMemory"), TEXT("bytes"),
GetTextureMemoryUsage());
MICROMEGAS_FMETRIC¶
Records a floating-point metric value.
Parameters:
target(const char*): Metric target/categorylevel(MicromegasTracing::Verbosity): Verbosity levelname(const TCHAR*): Metric nameunit(const TCHAR*): Unit of measurementexpression(double): Value or expression to record
Example:
MICROMEGAS_FMETRIC("Performance", MicromegasTracing::Verbosity::Med,
TEXT("FrameTime"), TEXT("ms"),
DeltaTime * 1000.0);
MICROMEGAS_FMETRIC("Game", MicromegasTracing::Verbosity::Max,
TEXT("HealthPercent"), TEXT("percent"),
(Health / MaxHealth) * 100.0);
Spans/Tracing API¶
Important: Spans are disabled by default in the editor and enabled by default in non-editor (game) builds. Toggle with the console command telemetry.spans.enable 0/1. Use a reasonable sampling strategy for high-frequency spans.
MICROMEGAS_SPAN_FUNCTION¶
Traces the current function's execution time using the function name as the span name.
Parameters:
target(const char*): Span target/category
Example:
void AMyActor::ComplexCalculation()
{
MICROMEGAS_SPAN_FUNCTION("Game.Physics");
// Function is automatically traced
// ... complex physics calculations ...
}
MICROMEGAS_SPAN_SCOPE¶
Creates a named scope span with a static name.
Parameters:
target(const char*): Span target/categoryname(const char*): Static span name
Example:
void ProcessAI()
{
{
MICROMEGAS_SPAN_SCOPE("AI", "Pathfinding");
// ... pathfinding code ...
}
{
MICROMEGAS_SPAN_SCOPE("AI", "DecisionTree");
// ... decision tree evaluation ...
}
}
MICROMEGAS_SPAN_NAME¶
Creates a span with a dynamic name (must be statically allocated).
Parameters:
target(const char*): Span target/categoryname_expression: Expression returning a statically allocated string (e.g., FName)
Example:
void ProcessAsset(const FString& AssetPath)
{
FName AssetName(*AssetPath);
MICROMEGAS_SPAN_NAME("Content", AssetName);
// ... process asset ...
}
MICROMEGAS_SPAN_UOBJECT¶
Creates a span named after a UObject.
Parameters:
target(const char*): Span target/categoryobject(UObject*): The UObject whose name to use
Example:
void AMyActor::Tick(float DeltaTime)
{
MICROMEGAS_SPAN_UOBJECT("Game.Actors", this);
Super::Tick(DeltaTime);
// ... tick logic ...
}
MICROMEGAS_SPAN_UOBJECT_CONDITIONAL¶
Creates a span named after a UObject, but only when a condition is true.
Parameters:
target(const char*): Span target/categorycondition(bool): Whether to create the spanobject(UObject*): The UObject whose name to use if condition is true
Example:
void AMyActor::Tick(float DeltaTime)
{
MICROMEGAS_SPAN_UOBJECT_CONDITIONAL("Game.Actors", bIsImportant, this);
Super::Tick(DeltaTime);
}
MICROMEGAS_SPAN_NAME_CONDITIONAL¶
Creates a span conditionally.
Parameters:
target(const char*): Span target/categorycondition(bool): Whether to create the spanname: Span name if condition is true
Example:
void RenderFrame(bool bDetailedProfiling)
{
MICROMEGAS_SPAN_NAME_CONDITIONAL("Render", bDetailedProfiling,
TEXT("DetailedFrame"));
// ... rendering code ...
}
Network Tracing¶
Audience
This section is written to be readable by both human integrators and coding-agent LLMs (e.g. Claude Code). Each macro entry follows a fixed shape — signature, parameters, semantics, bit-source expression, example — so an agent can apply each site without ambiguity.
For the engine-side recipe — which UE files to modify and where — see Network Tracing.
Net-trace macros capture per-connection replication traffic with bit-size attribution. They live in the same header as logs/metrics/spans:
All macros are RAII where applicable; early returns close scopes automatically. When MICROMEGAS_NET_TRACE_ENABLED is 0, every macro expands to nothing — zero overhead.
Bit-source cheat sheet¶
Every scope macro that takes a getBitsExpr parameter captures the position at entry and measures the delta at exit. The expression must refer to a bit stream that's being written to (send) or read from (receive) inside the scope.
| Situation | Expression |
|---|---|
| Classic send (outgoing bunch) | Bunch.GetNumBits() |
| Classic receive (incoming reader) | Reader.GetPosBits() |
Classic RPC send (TempWriter) |
TempWriter.GetNumBits() |
| Classic fast-array property writer | TempBitWriter.GetNumBits() |
| Iris send | Context.GetBitStreamWriter()->GetPosBits() |
| Iris receive | Context.GetBitStreamReader()->GetPosBits() |
Flat property calls (MICROMEGAS_NET_PROPERTY) pass the pre-computed bit size directly — no bit stream expression needed.
MICROMEGAS_NET_CONNECTION_SCOPE¶
Opens a per-connection scope. All object/property/RPC events emitted inside are attributed to this connection.
Parameters:
ConnectionName(FName): stable connection identifier — read from a cachedMmDisplayNamemember onUNetConnectionpopulated at lifecycle hooks (see Network Tracing § 3)bIsOutgoing(bool):truefor send paths,falsefor receive paths
Semantics:
- RAII; closes on scope exit
- Connection scopes do not nest — only the outermost emits, inner ones are absorbed as no-ops and logged once via
LogMicromegasNet - Snapshots the current runtime verbosity at the outermost Begin; CVar changes take effect at the next outer scope
Emits: NetConnectionBeginEvent on entry, NetConnectionEndEvent (with bit_size = sum of root object/RPC bits) on exit.
Example:
void UNetConnection::ReceivedPacket(FBitReader& Reader)
{
MICROMEGAS_NET_CONNECTION_SCOPE(MmDisplayName, /*bIsOutgoing=*/ false);
// ... packet processing ...
}
MmDisplayName is a cached FName member added to UNetConnection, refreshed at lifecycle hooks (handshake complete, OnRep_PlayerState, OnRep_PlayerName, etc.) — see Network Tracing § 3 — Connection name strategy for the resolution chain and refresh sites.
MICROMEGAS_NET_OBJECT_SCOPE¶
Opens a per-object scope (root actor or subobject). Measures bit-stream delta from entry to exit.
Parameters:
ObjectName:FNameorconst TCHAR*(anything acceptable toStaticStringRef)getBitsExpr: expression returning the current bit-stream position (see cheat sheet above)
Semantics:
- RAII; on destruction emits
NetObjectEndEventwithbit_size = GetBits() - StartBits - Depth 0 (root) requires verbosity ≥
RootObjects; depth 1+ requires ≥Objects - Classic emits subobjects as peers at depth 0; Iris emits them nested at depth 1+
Emits: NetObjectBeginEvent on entry, NetObjectEndEvent on exit.
Example (classic send):
Example (Iris send, with null guard):
MICROMEGAS_NET_OBJECT_SCOPE(
(ObjectData.Protocol && ObjectData.Protocol->DebugName) ? ObjectData.Protocol->DebugName->Name : TEXT("Unknown"),
Context.GetBitStreamWriter()->GetPosBits());
MICROMEGAS_NET_OBJECT_EVENT¶
Fire-and-forget object event. Emits a NetObjectBeginEvent / NetObjectEndEvent pair immediately with the supplied bit count — no scope, no diff capture. Zero-bit calls are suppressed.
Parameters:
ObjectName:FNameorconst TCHAR*Bits(uint32): the object's bit size, known up front
Semantics:
- No RAII scope; both events fire at the call site
- Use when the bit count is already known and the wrapped code does not mutate it (wire-framing classes like packet headers, bunch headers, padding, NetGUID exports)
- Prefer
MICROMEGAS_NET_OBJECT_SCOPEwhen bits are produced by code running inside the scope — the diff form picks them up automatically
Example (per-bunch header overhead):
const uint32 PreHeaderBits = SendBuffer.GetNumBits();
SerializeBunchHeader(SendBuffer, Bunch);
MICROMEGAS_NET_OBJECT_EVENT(TEXT("BunchHeader"), SendBuffer.GetNumBits() - PreHeaderBits);
MICROMEGAS_NET_OBJECT_SIZE_SCOPE¶
RAII scope that emits NetObjectEndEvent with the value returned by GetSizeExpr at destruction — not a delta against a captured start position. Use when a wrapped call consumes a pre-built bunch without mutating its bit count, where the standard MICROMEGAS_NET_OBJECT_SCOPE diff would observe 0 and elide.
Parameters:
ObjectName:FNameorconst TCHAR*GetSizeExpr: expression returning the object's full bit size on scope exit
Semantics:
- RAII; emits Begin on entry, End with
GetSize()(not a diff) on destruction - The canonical use is retransmits:
SendRawBunch(Bunch)does not changeBunch->GetNumBits(), so a diff scope reads 0 and the writer's elision path drops the event
Example (NAK retransmit):
MICROMEGAS_NET_OBJECT_SIZE_SCOPE(TEXT("Retransmit"), Bunch->GetNumBits());
SendRawBunch(*Bunch, /*bMustBeReliable=*/ false);
MICROMEGAS_NET_RPC_SCOPE¶
Opens a per-RPC scope. Same shape as object scope but emits NetRPCBeginEvent / NetRPCEndEvent.
Parameters:
FunctionName:FNameorconst TCHAR*getBitsExpr: expression returning the current bit-stream position
Semantics:
- RAII;
EndRPCapplies anObjectDepth == 0gate that prevents double-counting when an RPC fires inside an object scope (nested RPC bits roll into the outer object, not double-attributed to the connection)
Example (classic send):
Example (Iris receive, post-resolve):
MICROMEGAS_NET_RPC_SCOPE(BlobDescriptor->DebugName->Name,
Context.GetBitStreamReader()->GetPosBits());
MICROMEGAS_NET_PROPERTY¶
Flat property leaf — emits a single NetPropertyEvent with the pre-computed bit size. No Begin/End pair.
Parameters:
PropertyName:FNameorconst TCHAR*bitSize(uint32): pre-computed bit length
Semantics:
- Gated at verbosity ≥
Properties - Use when the bit size is already known (e.g.
SharedPropInfo->PropBitLength, or aNumEndBits - NumStartBitsdelta captured forNETWORK_PROFILER)
Example:
MICROMEGAS_NET_PROPERTY_SCOPE¶
Scope-form property — measures the bit-stream delta across the wrapped serialize/deserialize call and emits a single NetPropertyEvent on destruction (still a leaf, no Begin/End pair).
Parameters:
PropertyName:FNameorconst TCHAR*getBitsExpr: bit-stream position expression (see cheat sheet)
Semantics:
- Use when no pre-computed bit size is available (Iris properties, classic receive paths)
- The scope only emits on destruction, after the wrapped serializer call has run
Example (classic receive):
Example (Iris receive):
MICROMEGAS_NET_PROPERTY_SCOPE(MemberDebugDescriptors[MemberIt].DebugName->Name,
Context.GetBitStreamReader()->GetPosBits());
MICROMEGAS_NET_SUSPEND_SCOPE¶
Zeroes out every MICROMEGAS_NET_* call inside its lifetime without touching depth counters. Safe to nest under an active live scope.
Parameters: none.
Semantics:
- RAII;
Dispatch::NetSuspend()on entry,NetResume()on exit - Use for code paths that process packets/bunches but shouldn't contribute to attribution: demo recording, replay scrubbing, server-side simulation
Example:
void UDemoNetDriver::ProcessRemoteFunction(...)
{
MICROMEGAS_NET_SUSPEND_SCOPE();
InternalProcessRemoteFunction(...);
}
Verbosity Levels¶
Runtime verbosity is a 0–4 enum. Depth-based gating inside NetTraceWriter:
| Level | Name | Emits |
|---|---|---|
| 0 | Off |
Nothing |
| 1 | Packets |
Connection scopes only |
| 2 | RootObjects |
+ root object scopes (depth 0) |
| 3 | Objects |
+ nested object scopes (depth 1+) |
| 4 | Properties |
+ per-property leaf events, + RPC scopes |
Default: level 2 (RootObjects) — production setting.
Root RPC bits (ObjectDepth == 0) still contribute to NetConnectionEndEvent.bit_size at every verbosity ≥ Packets, even though NetRPCBeginEvent / NetRPCEndEvent records are only emitted at level 4.
Snapshot invariant: the writer captures EffectiveVerbosity at the outermost BeginConnection and uses that snapshot for every gating decision in the scope. CVar-driven changes take effect at the next outer scope, never mid-scope.
Console & Command Line¶
- CVar:
telemetry.net.verbosity <0-4>— sets runtime verbosity. Effective at the next outer connection scope. - Command-line flag:
-MicromegasNetTrace=N— sets initial verbosity at process start.
Physical packet metrics¶
Two integer metrics are emitted via MICROMEGAS_IMETRIC from the instrumented engine code (see Network Tracing § 3.1, § 3.2):
net.packet_sent_bits(unitbits) —SendBuffer.GetNumBits()inFlushNetnet.packet_received_bits(unitbits) —Reader.GetNumBits()inReceivedPacket
These are wire bits including packet headers, bunch headers, NetGUID exports, control bunches, and voice. Compare against sum(NetConnectionEndEvent.bit_size) for content-vs-wire reconciliation — the gap is framing overhead.
Image API¶
Dispatch::SendImage¶
Sends an image as a telemetry event. Images are stored in the images view and can be queried via SQL or viewed in the notebook Image cell.
Parameters:
Name(const TCHAR*): Image name / label (e.g.,TEXT("screenshot"))Format(const TCHAR*): MIME type (e.g.,TEXT("image/png"))Data(const uint8*): Raw image bytesDataBytes(uint32): Length ofDatain bytes
Example — send a PNG from a byte array:
#include "MicromegasTracing/Dispatch.h"
TArray64<uint8> PngBytes = EncodeAsPng(Width, Height, Pixels);
MicromegasTracing::Dispatch::SendImage(
TEXT("heatmap"),
TEXT("image/png"),
PngBytes.GetData(),
static_cast<uint32>(PngBytes.Num()));
Console command shortcut:
The telemetry.screenshot console command captures the game viewport and calls SendImage automatically:
This works in both game builds (via UGameViewportClient::OnScreenshotCaptured) and in the editor (via GEditor->GetActiveViewport()->ReadPixels). Use telemetry.images.enable 0 to suppress image recording globally.
Default Context API¶
The Default Context allows setting global properties that are automatically attached to all telemetry.
Accessing the Default Context¶
Set¶
Adds or updates a context property.
Example:
if (auto* Ctx = MicromegasTracing::Dispatch::GetDefaultContext())
{
Ctx->Set(FName("user_id"), FName(*UserId));
Ctx->Set(FName("session_id"), FName(*SessionId));
Ctx->Set(FName("map"), FName(*GetWorld()->GetMapName()));
}
Unset¶
Removes a context property.
Example:
Clear¶
Removes all context properties.
Example:
Copy¶
Copies current context to a map.
Example:
Console Commands¶
Runtime control commands and CVars available in the Unreal console:
| Command / CVar | Default | Description |
|---|---|---|
telemetry.enable |
— | Initialize the telemetry system |
telemetry.flush |
— | Force flush all pending events |
telemetry.spans.enable |
true (game), false (editor) |
Enable/disable span recording |
telemetry.spans.all |
false |
Record all spans without sampling |
telemetry.log.enable |
true |
Enable/disable log stream recording |
telemetry.metrics.enable |
true |
Enable/disable metrics stream recording |
telemetry.images.enable |
true |
Enable/disable images sent via SendImage |
telemetry.screenshot |
— | Capture the game/editor viewport as a telemetry image |
telemetry.net.verbosity |
2 |
Net trace verbosity: 0=off, 1=packets, 2=+root objects, 3=+all objects, 4=+properties/RPCs |
telemetry.max_queue_bytes |
134217728 |
Soft queue cap in bytes; Traces dropped above this |
telemetry.hard_queue_bytes |
268435456 |
Hard queue ceiling; Logs/Metrics also dropped above this |
telemetry.max_in_flight_requests |
3 |
Max concurrent HTTP uploads in flight |
telemetry.sampling.interaction_timeout |
2.0 s |
Seconds of idle before spike recording is suppressed; 0 disables |
telemetry.sampling.heartbeat_interval |
120.0 s |
Seconds between heartbeat span captures; 0 disables |
Best Practices¶
Performance¶
- Use sampling for high-frequency spans — spike-based sampling (
telemetry.spans.all 0) captures performance anomalies without recording every frame - Use appropriate verbosity —
Verbosity::Minfor low-frequency events,Verbosity::Medfor per-frame,Verbosity::Maxfor sub-frame - Batch operations — let the system batch; avoid frequent manual flushes
- Static strings — use
TEXT()macro for string literals in metric/span names - Limit context cardinality — context keys/values are interned and never freed
- Queue caps — the HTTP sink has a soft cap (Traces dropped first) and hard cap (Logs/Metrics dropped too); adjust
telemetry.max_queue_bytesif you seeDroppedUploadsmetrics during outages
Error Handling¶
Always check for null pointers when using the context API:
if (auto* Ctx = MicromegasTracing::Dispatch::GetDefaultContext())
{
// Safe to use Ctx
Ctx->Set(FName("key"), FName("value"));
}
Thread Safety¶
All Micromegas APIs are thread-safe and can be called from any thread:
// Safe from game thread
MICROMEGAS_LOG("Game", MicromegasTracing::LogLevel::Info, TEXT("Game thread"));
// Safe from render thread
MICROMEGAS_LOG("Render", MicromegasTracing::LogLevel::Info, TEXT("Render thread"));
// Safe from worker threads
ParallelFor(NumItems, [](int32 Index)
{
MICROMEGAS_IMETRIC("Worker", MicromegasTracing::Verbosity::Max,
TEXT("ItemProcessed"), TEXT("count"), 1);
});
Integration Examples¶
With Gameplay Abilities¶
void UMyGameplayAbility::ActivateAbility(...)
{
MICROMEGAS_SPAN_NAME("Abilities", GetFName());
MICROMEGAS_LOG("Abilities", MicromegasTracing::LogLevel::Info,
FString::Printf(TEXT("Ability %s activated"), *GetName()));
Super::ActivateAbility(...);
}
With Animation¶
void UAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
MICROMEGAS_SPAN_FUNCTION("Animation");
MICROMEGAS_FMETRIC("Animation", MicromegasTracing::Verbosity::Max,
TEXT("UpdateTime"), TEXT("ms"), DeltaSeconds * 1000);
Super::NativeUpdateAnimation(DeltaSeconds);
}