diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs
index 6f3bd006c1..293836d22d 100644
--- a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs
+++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs
@@ -298,7 +298,7 @@ private void DisplayNetworkManagerProperties()
#else
string path = Path.Combine(directory, $"NetworkPrefabs-{m_NetworkManager.GetInstanceID()}.asset");
#endif
- Debug.Log("Saving migrated Network Prefabs List to " + path);
+ m_NetworkManager.Log.Info(new Context(LogLevel.Normal, "Saving migrated Network Prefabs List").With("Path", path));
AssetDatabase.CreateAsset(networkPrefabs, path);
EditorUtility.SetDirty(m_NetworkManager);
}
diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs
index cdd4b06186..4352749f5f 100644
--- a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs
+++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs
@@ -162,19 +162,17 @@ public void CheckAndNotifyUserNetworkObjectRemoved(NetworkManager networkManager
if (!EditorApplication.isPlaying && !editorTest)
{
- EditorUtility.DisplayDialog($"Removing {nameof(NetworkObject)}", NetworkManagerAndNetworkObjectNotAllowedMessage(), "OK");
+ EditorUtility.DisplayDialog($"Removing {nameof(NetworkObject)}", k_NetworkManagerAndNetworkObjectNotAllowedMessage, "OK");
}
else
{
- Debug.LogError(NetworkManagerAndNetworkObjectNotAllowedMessage());
+ networkManager.Log.Error(new Context(LogLevel.Error, k_NetworkManagerAndNetworkObjectNotAllowedMessage));
}
}
}
- public string NetworkManagerAndNetworkObjectNotAllowedMessage()
- {
- return $"A {nameof(GameObject)} cannot have both a {nameof(NetworkManager)} and {nameof(NetworkObject)} assigned to it or any children under it.";
- }
+ private static readonly string k_NetworkManagerAndNetworkObjectNotAllowedMessage = $"A {nameof(GameObject)} cannot have both a {nameof(NetworkManager)} and {nameof(NetworkObject)} assigned to it or any children under it.";
+ public string NetworkManagerAndNetworkObjectNotAllowedMessage() => k_NetworkManagerAndNetworkObjectNotAllowedMessage;
///
/// Handles notifying the user, via display dialog window, that they have nested a NetworkManager.
@@ -215,7 +213,7 @@ public bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool
}
else
{
- Debug.LogError(message);
+ networkManager.Log.Error(new Context(LogLevel.Error, message));
}
if (!s_LastKnownNetworkManagerParents.ContainsKey(networkManager) && isParented)
diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs
index d8bcfaffff..14ee04ea0b 100644
--- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs
@@ -73,10 +73,7 @@ internal static void LogSerializedTypeNotOptimized()
if (!s_SerializedType.Contains(type))
{
s_SerializedType.Add(type);
- if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
- {
- Debug.LogWarning($"[{type.Name}] Serialized type has not been optimized for use with Distributed Authority!");
- }
+ NetworkLog.LogWarning(new Context(LogLevel.Developer, "Serialized type has not been optimized for use with Distributed Authority!").With(type.Name));
}
}
#endif
@@ -148,7 +145,7 @@ internal GameObject FetchLocalPlayerPrefabToSpawn()
{
if (!AutoSpawnPlayerPrefabClientSide)
{
- Debug.LogError($"[{nameof(FetchLocalPlayerPrefabToSpawn)}] Invoked when {nameof(NetworkConfig.AutoSpawnPlayerPrefabClientSide)} was not set! Check call paths!");
+ Log.Error(new Context(LogLevel.Error, $"Invoked when {nameof(NetworkConfig.AutoSpawnPlayerPrefabClientSide)} was not set! Check call paths!"));
return null;
}
if (OnFetchLocalPlayerPrefabToSpawn == null && NetworkConfig.PlayerPrefab == null)
@@ -244,12 +241,13 @@ internal void PromoteSessionOwner(ulong clientId)
{
if (!DistributedAuthorityMode)
{
- NetworkLog.LogErrorServer($"[SceneManagement][NotDA] Invoking promote session owner while not in distributed authority mode!");
+ // [Netcode] [PromoteSessionOwner][SceneManagement][NotDA] Invoking promote session owner while not in distributed authority mode!
+ Log.ErrorServer(new Context(LogLevel.Error, "Invoking promote session owner while not in distributed authority mode!").With("SceneManagement").With("NotDA"));
return;
}
if (!DAHost)
{
- NetworkLog.LogErrorServer($"[SceneManagement][NotDAHost] Client is attempting to promote another client as the session owner!");
+ Log.ErrorServer(new Context(LogLevel.Error, "Client is attempting to promote another client as the session owner!").With("SceneManagement").With("NotDAHost"));
return;
}
SetSessionOwner(clientId);
@@ -316,7 +314,8 @@ private void UpdateTopology()
var transportTopology = IsListening && IsConnectedClient ? NetworkConfig.NetworkTransport.CurrentTopology() : NetworkConfig.NetworkTopology;
if (transportTopology != NetworkConfig.NetworkTopology)
{
- NetworkLog.LogErrorServer($"[Topology Mismatch][{transportTopology}:{transportTopology.GetType().Name}][NetworkManager.NetworkConfig:{NetworkConfig.NetworkTopology}] Transport detected an issue with the topology usage or setting! Disconnecting from session.");
+ Log.ErrorServer(new Context(LogLevel.Error, "Transport detected an issue with the topology usage or setting! Disconnecting from session.")
+ .With("Topology Mismatch").With(transportTopology, transportTopology.GetType().Name).With("NetworkManager.NetworkConfig", NetworkConfig.NetworkTopology));
Shutdown(true);
}
else
@@ -892,6 +891,12 @@ public struct ConnectionApprovalRequest
///
public event Action OnClientStarted = null;
+ ///
+ /// The callback to invoke once started
+ /// Invoked on both the server and the client
+ ///
+ internal event Action OnStarted = null;
+
///
/// Subscribe to this event to get notifications before a instance is being destroyed.
/// This is useful if you want to use the state of anything the NetworkManager cleans up during its shutdown.
@@ -909,6 +914,12 @@ public struct ConnectionApprovalRequest
/// The parameter states whether the client was running in host mode
public event Action OnClientStopped = null;
+ ///
+ /// The callback to invoke once the session stops
+ /// Invoked on both the server and the client
+ ///
+ internal event Action OnStopped = null;
+
///
/// The instance created after starting the
///
@@ -984,6 +995,7 @@ public NetworkPrefabHandler PrefabHandler
///
internal IRealTimeProvider RealTimeProvider { get; private set; }
+ internal ContextualLogger Log;
internal INetworkMetrics NetworkMetrics => MetricsManager.NetworkMetrics;
internal NetworkMetricsManager MetricsManager = new NetworkMetricsManager();
internal NetworkConnectionManager ConnectionManager = new NetworkConnectionManager();
@@ -1040,6 +1052,11 @@ public void SetSingleton()
private void Awake()
{
+ if (Log == null)
+ {
+ Log = new ContextualLogger(this, gameObject);
+ }
+
NetworkConfig?.InitializePrefabs();
UnityEngine.SceneManagement.SceneManager.sceneUnloaded += OnSceneUnloaded;
@@ -1178,19 +1195,12 @@ internal void Initialize(bool server)
if (NetworkConfig.NetworkTransport == null)
{
- if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
- {
- NetworkLog.LogError("No transport has been selected!");
- }
-
+ Log.Error(new Context(LogLevel.Error, "No transport has been selected!"));
return;
}
// Logging initializes first for any logging during systems initialization
- if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
- {
- NetworkLog.LogInfo(nameof(Initialize));
- }
+ Log.CaptureFunctionCall();
this.RegisterNetworkUpdate(NetworkUpdateStage.EarlyUpdate);
#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
@@ -1275,11 +1285,7 @@ private bool CanStart(StartType type)
{
if (IsListening)
{
- if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
- {
- NetworkLog.LogWarning("Cannot start " + type + " while an instance is already running");
- }
-
+ Log.Warning(new Context(LogLevel.Normal, "Can't start while listening").With("Start", type));
return false;
}
@@ -1289,10 +1295,7 @@ private bool CanStart(StartType type)
{
if (ConnectionApprovalCallback == null)
{
- if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
- {
- NetworkLog.LogWarning("No ConnectionApproval callback defined. Connection approval will timeout");
- }
+ Log.Warning(new Context(LogLevel.Normal, $"No {nameof(ConnectionApprovalCallback)} defined. Connection approval will timeout").With("Start", type));
}
}
@@ -1300,10 +1303,7 @@ private bool CanStart(StartType type)
{
if (!NetworkConfig.ConnectionApproval)
{
- if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
- {
- NetworkLog.LogWarning("A ConnectionApproval callback is defined but ConnectionApproval is disabled. In order to use ConnectionApproval it has to be explicitly enabled ");
- }
+ Log.Warning(new Context(LogLevel.Normal, $"{nameof(ConnectionApprovalCallback)} is defined but {nameof(NetworkConfig.ConnectionApproval)} is disabled. In order to use ConnectionApproval it has to be explicitly enabled").With("Start", type));
}
}
@@ -1313,13 +1313,10 @@ private bool CanStart(StartType type)
///
/// Starts a server
///
- /// (/) returns true if started in server mode successfully.
+ /// returns true if started in server mode successfully; otherwise false
public bool StartServer()
{
- if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
- {
- NetworkLog.LogInfo(nameof(StartServer));
- }
+ Log.CaptureFunctionCall();
if (!CanStart(StartType.Server))
{
@@ -1338,7 +1335,7 @@ public bool StartServer()
}
catch (Exception ex)
{
- Debug.LogException(ex);
+ Log.Exception(ex);
// Always shutdown to assure everything is cleaned up
ShutdownInternal();
return false;
@@ -1355,6 +1352,7 @@ public bool StartServer()
// Notify the server that everything should be synchronized/spawned at this time.
SpawnManager.NotifyNetworkObjectsSynchronized();
OnServerStarted?.Invoke();
+ OnStarted?.Invoke();
ConnectionManager.LocalClient.IsApproved = true;
return true;
}
@@ -1363,7 +1361,7 @@ public bool StartServer()
}
catch (Exception ex)
{
- Debug.LogException(ex);
+ Log.Exception(ex);
// Always shutdown to assure everything is cleaned up
ShutdownInternal();
IsListening = false;
@@ -1378,10 +1376,7 @@ public bool StartServer()
/// (/) returns true if started in client mode successfully.
public bool StartClient()
{
- if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
- {
- NetworkLog.LogInfo(nameof(StartClient));
- }
+ Log.CaptureFunctionCall();
if (!CanStart(StartType.Client))
{
@@ -1399,7 +1394,7 @@ public bool StartClient()
}
catch (Exception ex)
{
- Debug.LogException(ex);
+ Log.Exception(ex);
ShutdownInternal();
return false;
}
@@ -1415,11 +1410,12 @@ public bool StartClient()
else
{
OnClientStarted?.Invoke();
+ OnStarted?.Invoke();
}
}
catch (Exception ex)
{
- Debug.LogException(ex);
+ Log.Exception(ex);
ShutdownInternal();
IsListening = false;
}
@@ -1433,10 +1429,7 @@ public bool StartClient()
/// (/) returns true if started in host mode successfully.
public bool StartHost()
{
- if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
- {
- NetworkLog.LogInfo(nameof(StartHost));
- }
+ Log.CaptureFunctionCall();
if (!CanStart(StartType.Host))
{
@@ -1454,7 +1447,7 @@ public bool StartHost()
}
catch (Exception ex)
{
- Debug.LogException(ex);
+ Log.Exception(ex);
// Always shutdown to assure everything is cleaned up
ShutdownInternal();
return false;
@@ -1476,7 +1469,7 @@ public bool StartHost()
}
catch (Exception ex)
{
- Debug.LogException(ex);
+ Log.Exception(ex);
// Always shutdown to assure everything is cleaned up
ShutdownInternal();
IsListening = false;
@@ -1501,10 +1494,7 @@ private void HostServerInitialize()
ConnectionApprovalCallback(new ConnectionApprovalRequest { Payload = NetworkConfig.ConnectionData, ClientNetworkId = ServerClientId }, response);
if (!response.Approved)
{
- if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
- {
- NetworkLog.LogWarning("You cannot decline the host connection. The connection was automatically approved.");
- }
+ Log.Warning(new Context(LogLevel.Normal, "You cannot decline the host connection. The connection was automatically approved."));
}
ConnectionManager.HandleConnectionApproval(ServerClientId, response.CreatePlayerObject, response.PlayerPrefabHash, response.Position, response.Rotation);
@@ -1523,6 +1513,7 @@ private void HostServerInitialize()
OnServerStarted?.Invoke();
OnClientStarted?.Invoke();
+ OnStarted?.Invoke();
// This assures that any in-scene placed NetworkObject is spawned and
// any associated NetworkBehaviours' netcode related properties are
@@ -1582,10 +1573,7 @@ public ulong GetClientIdFromTransportId(ulong transportId)
///
public void Shutdown(bool discardMessageQueue = false)
{
- if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
- {
- NetworkLog.LogInfo(nameof(Shutdown));
- }
+ Log.CaptureFunctionCall();
// If we're not running, don't start shutting down, it would only cause an immediate
// shutdown the next time the manager is started.
@@ -1613,10 +1601,7 @@ internal void ShutdownInternal()
#if UNITY_EDITOR
EndNetworkSession();
#endif
- if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
- {
- NetworkLog.LogInfo(nameof(ShutdownInternal));
- }
+ Log.CaptureFunctionCall();
// Always wrap events that can invoke user script in a
// try-catch to assure any proceeding script is still
@@ -1632,7 +1617,7 @@ internal void ShutdownInternal()
}
catch (Exception ex)
{
- Debug.LogException(ex);
+ Log.Exception(ex);
}
this.UnregisterAllNetworkUpdates();
@@ -1718,6 +1703,8 @@ internal void ShutdownInternal()
// or not. (why we pass in "IsClient")
OnServerStopped?.Invoke(localClient.IsClient);
}
+
+ OnStopped?.Invoke();
}
// Ensures that the NetworkManager is cleaned up before OnDestroy is run on NetworkObjects and NetworkBehaviours when quitting the application.
@@ -1738,7 +1725,7 @@ private void OnApplicationQuit()
#if UNITY_EDITOR
if (Singleton != null)
{
- Debug.LogWarning($"[nameof({nameof(OnApplicationQuit)}][{nameof(NetworkManager)}][{name}] Singleton is not null after invoking OnDestroy. Singleton instance name is {Singleton.name}. Do you have more than one {nameof(NetworkManager)} instance in the DDOL scene?");
+ Log.Warning(new Context(LogLevel.Error, $"Singleton is not null after invoking OnDestroy. Do you have more than one {nameof(NetworkManager)} instance in the DDOL scene?").With("SingletonInstance", Singleton.name));
}
#endif
}
@@ -1752,7 +1739,7 @@ private void OnDestroy()
}
catch (Exception ex)
{
- Debug.LogException(ex);
+ Log.Exception(ex);
}
UnityEngine.SceneManagement.SceneManager.sceneUnloaded -= OnSceneUnloaded;
@@ -1766,7 +1753,7 @@ private void OnDestroy()
}
catch (Exception ex)
{
- Debug.LogException(ex);
+ Log.Exception(ex);
}
if (Singleton == this)
@@ -1835,15 +1822,17 @@ internal void OnValidate()
return; // May occur when the component is added
}
+ if (Log == null)
+ {
+ Log = new ContextualLogger(this, gameObject);
+ }
+
// Do a validation pass on NetworkConfig properties
NetworkConfig.OnValidate();
if (GetComponentInChildren() != null)
{
- if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
- {
- NetworkLog.LogWarning($"{nameof(NetworkManager)} cannot be a {nameof(NetworkObject)}.");
- }
+ Log.Warning(new Context(LogLevel.Normal, $"{nameof(NetworkManager)} cannot be a {nameof(NetworkObject)}."));
}
var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
@@ -1859,9 +1848,8 @@ internal void OnValidate()
var prefabs = NetworkConfig.Prefabs.Prefabs;
// Check network prefabs and assign to dictionary for quick look up
- for (int i = 0; i < prefabs.Count; i++)
+ foreach (var networkPrefab in prefabs)
{
- var networkPrefab = prefabs[i];
var networkPrefabGo = networkPrefab?.Prefab;
if (networkPrefabGo == null)
{
@@ -1871,11 +1859,7 @@ internal void OnValidate()
var networkObject = networkPrefabGo.GetComponent();
if (networkObject == null)
{
- if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
- {
- NetworkLog.LogError($"Cannot register {NetworkPrefabHandler.PrefabDebugHelper(networkPrefab)}, it does not have a {nameof(NetworkObject)} component at its root");
- }
-
+ Log.Warning(new Context(LogLevel.Normal, $"Cannot register prefab to {nameof(NetworkManager)}, missing a {nameof(NetworkObject)} component at its root").ForNetworkPrefab(networkPrefab));
continue;
}
@@ -1884,10 +1868,7 @@ internal void OnValidate()
networkPrefabGo.GetComponentsInChildren(true, childNetworkObjects);
if (childNetworkObjects.Count > 1) // total count = 1 root NetworkObject + n child NetworkObjects
{
- if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
- {
- NetworkLog.LogWarning($"{NetworkPrefabHandler.PrefabDebugHelper(networkPrefab)} has child {nameof(NetworkObject)}(s) but they will not be spawned across the network (unsupported {nameof(NetworkPrefab)} setup)");
- }
+ Log.Warning(new Context(LogLevel.Normal, $"Prefab has child {nameof(NetworkObject)}(s) but they will not be spawned across the network (unsupported {nameof(NetworkPrefab)} setup)").ForNetworkPrefab(networkPrefab));
}
}
}
@@ -1898,7 +1879,7 @@ internal void OnValidate()
}
catch (Exception ex)
{
- Debug.LogException(ex);
+ Log.Exception(ex);
}
}
@@ -1917,7 +1898,7 @@ internal void ModeChanged(PlayModeStateChange playModeState)
}
catch (Exception ex)
{
- Debug.LogException(ex);
+ Log.Exception(ex);
}
}
@@ -1933,7 +1914,7 @@ private void BeginNetworkSession()
}
catch (Exception ex)
{
- Debug.LogException(ex);
+ Log.Exception(ex);
}
}
@@ -1948,7 +1929,7 @@ private void EndNetworkSession()
}
catch (Exception ex)
{
- Debug.LogException(ex);
+ Log.Exception(ex);
}
}
#endif
diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs b/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs
new file mode 100644
index 0000000000..f5a671ce07
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs
@@ -0,0 +1,184 @@
+using System;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Text;
+using UnityEngine;
+using Debug = UnityEngine.Debug;
+using LogType = UnityEngine.LogType;
+
+namespace Unity.Netcode
+{
+ internal class ContextualLogger
+ {
+ private const string k_NetcodeHeader = "[Netcode] ";
+ private bool m_UseCompatibilityMode;
+ private readonly GameObject m_GameObject;
+ private readonly ContextBuilder m_Builder = new();
+
+ private LogContextNetworkManager m_ManagerContext;
+ private readonly GenericContext m_LoggerContext;
+
+ private const string k_CompilationCondition = "UNITY_ASSERTIONS";
+
+ public ContextualLogger(bool useCompatibilityMode = false)
+ {
+ m_UseCompatibilityMode = useCompatibilityMode;
+ m_ManagerContext = new LogContextNetworkManager(true);
+ m_GameObject = null;
+ m_LoggerContext = GenericContext.Create();
+ }
+
+ public ContextualLogger([NotNull] NetworkManager networkManager, GameObject gameObject)
+ {
+ m_ManagerContext = new LogContextNetworkManager(networkManager);
+ m_GameObject = gameObject;
+ m_LoggerContext = GenericContext.Create();
+ }
+
+ [Conditional(k_CompilationCondition)]
+ internal void UpdateNetworkManagerContext(NetworkManager manager)
+ {
+ m_ManagerContext.Dispose();
+ m_ManagerContext = new LogContextNetworkManager(manager);
+ }
+
+ [Conditional(k_CompilationCondition)]
+ internal void PushContext(string key, object value)
+ {
+ m_LoggerContext.StoreInfo(key, value);
+ }
+
+ [Conditional(k_CompilationCondition)]
+ internal void PushContext(string key)
+ {
+ m_LoggerContext.StoreContext(key);
+ }
+
+ [Conditional(k_CompilationCondition)]
+ internal void PopContext(string key)
+ {
+ m_LoggerContext.ClearInfo(key);
+ }
+
+
+ [HideInCallstack]
+ [Conditional(k_CompilationCondition)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void CaptureFunctionCall([CallerMemberName] string memberName = "")
+ {
+ Log(LogType.Log, new Context(LogLevel.Developer, memberName, true));
+ }
+
+ [HideInCallstack]
+ [Conditional(k_CompilationCondition)]
+ public void Info(Context context) => Log(LogType.Log, context);
+ [HideInCallstack]
+ [Conditional(k_CompilationCondition)]
+ public void Warning(Context context) => Log(LogType.Warning, context);
+ [HideInCallstack]
+ [Conditional(k_CompilationCondition)]
+ public void Error(Context context) => Log(LogType.Error, context);
+
+ [HideInCallstack]
+ [Conditional(k_CompilationCondition)]
+ public void InfoServer(Context context) => LogServer(LogType.Log, context);
+ [HideInCallstack]
+ [Conditional(k_CompilationCondition)]
+ public void WarningServer(Context context) => LogServer(LogType.Warning, context);
+ [HideInCallstack]
+ [Conditional(k_CompilationCondition)]
+ public void ErrorServer(Context context) => LogServer(LogType.Error, context);
+
+ [HideInCallstack]
+ public void Exception(Exception exception)
+ {
+ Debug.unityLogger.LogException(exception, m_GameObject);
+ }
+
+ [HideInCallstack]
+ private void Log(LogType logType, Context context)
+ {
+ // Don't act if the LogLevel is higher than the level of this log
+ if (m_ManagerContext.LogLevel > context.Level)
+ {
+ return;
+ }
+
+ var message = BuildLog(context);
+ Debug.unityLogger.Log(logType, (object)message, context.GameObjectOverride ?? m_GameObject);
+ }
+
+ [HideInCallstack]
+ private void LogServer(LogType logType, Context context)
+ {
+ // Don't act if the configured logging level is higher than the level of this log
+ if (m_ManagerContext.LogLevel <= context.Level)
+ {
+ return;
+ }
+
+ var message = BuildLog(context);
+ Debug.unityLogger.Log(logType, (object)message, context.GameObjectOverride ?? m_GameObject);
+
+ m_ManagerContext.TrySendMessage(logType, message);
+ }
+
+ private string BuildLog(Context context)
+ {
+ m_Builder.Reset();
+
+ // Add the Netcode prefix
+ m_Builder.Append(k_NetcodeHeader);
+
+ if (m_UseCompatibilityMode)
+ {
+ m_Builder.Append(context.Message);
+ }
+ else
+ {
+ // Add the system context
+ m_ManagerContext.AppendTo(m_Builder);
+ m_LoggerContext.AppendTo(m_Builder);
+
+ // Add the context for this log
+ context.AppendTo(m_Builder);
+ }
+
+ return m_Builder.Build();
+ }
+ }
+
+ internal class ContextBuilder
+ {
+ private readonly StringBuilder m_Builder = new();
+ private const string k_OpenBracket = "[";
+ private const string k_CloseBracket = "]";
+ private const string k_Separator = ":";
+
+ public void Reset()
+ {
+ m_Builder.Clear();
+ }
+
+ public void AppendContext(string context)
+ {
+ m_Builder.Append(k_OpenBracket);
+ m_Builder.Append(context);
+ m_Builder.Append(k_CloseBracket);
+ }
+
+ public void AppendContext(object key, object value)
+ {
+ m_Builder.Append(k_OpenBracket);
+ m_Builder.Append(key);
+ m_Builder.Append(k_Separator);
+ m_Builder.Append(value);
+ m_Builder.Append(k_CloseBracket);
+ }
+
+ public void Append(string value) => m_Builder.Append(value);
+
+ public string Build() => m_Builder.ToString();
+ }
+}
diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs.meta b/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs.meta
new file mode 100644
index 0000000000..4cdd3bc881
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: bbaa8fb0dd284e21b05ec68dd4e5e911
+timeCreated: 1776366667
\ No newline at end of file
diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/GenericContext.cs b/com.unity.netcode.gameobjects/Runtime/Logging/GenericContext.cs
new file mode 100644
index 0000000000..042d78e90c
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Logging/GenericContext.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+
+namespace Unity.Netcode
+{
+ internal readonly struct GenericContext : ILogContext, IDisposable
+ {
+ private readonly List m_Contexts;
+ private readonly Dictionary