From ad003b789410a72e40bd1d3e389f1cd0c5f7f2db Mon Sep 17 00:00:00 2001 From: Emma Date: Fri, 17 Apr 2026 13:16:54 -0400 Subject: [PATCH 01/16] chore: Make better logger --- .../Runtime/Logging/ContextualLogger.cs | 184 ++++++++++++++++++ .../Runtime/Logging/ContextualLogger.cs.meta | 3 + .../Runtime/Logging/GenericContext.cs | 86 ++++++++ .../Runtime/Logging/GenericContext.cs.meta | 3 + .../Runtime/Logging/LogContext.cs | 78 ++++++++ .../Runtime/Logging/LogContext.cs.meta | 3 + .../Logging/LogContextNetworkManager.cs | 110 +++++++++++ .../Logging/LogContextNetworkManager.cs.meta | 3 + .../Runtime/Logging/NetworkLog.cs | 161 ++++++++------- .../Messaging/Messages/ServerLogMessage.cs | 12 +- .../NetworkObjectDestroyTests.cs | 8 +- .../TestHelpers/NetcodeIntegrationTest.cs | 3 +- 12 files changed, 569 insertions(+), 85 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Logging/GenericContext.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Logging/GenericContext.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Logging/LogContextNetworkManager.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Logging/LogContextNetworkManager.cs.meta 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 m_Info; + + private GenericContext(List contexts, Dictionary info) + { + m_Contexts = contexts; + m_Info = info; + } + + public readonly void AppendTo(ContextBuilder builder) + { + if (m_Contexts != null) + { + foreach (var ctx in m_Contexts) + { + builder.AppendContext(ctx); + } + } + + if (m_Info != null) + { + foreach (var (key, value) in m_Info) + { + builder.AppendContext(key, value); + } + } + } + + public void StoreContext(string msg) + { + m_Contexts.Add(msg); + } + + public void StoreInfo(object key, object value) + { + m_Info.Add(key, value); + } + + public void ClearInfo(object key) + { + m_Info?.Remove(key); + } + + public void Dispose() + { + PreallocatedStore.Free(this); + } + + public static GenericContext Create() + { + return PreallocatedStore.GetPreallocated(); + } + + private static class PreallocatedStore + { + private static readonly Queue k_Preallocated = new(); + + internal static GenericContext GetPreallocated() + { + if (k_Preallocated.Count > 0) + { + k_Preallocated.Dequeue(); + } + + var contexts = new List(); + var info = new Dictionary(); + return new GenericContext(contexts, info); + } + + internal static void Free(GenericContext ctx) + { + ctx.m_Contexts.Clear(); + ctx.m_Info.Clear(); + k_Preallocated.Enqueue(ctx); + } + } + } + +} diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/GenericContext.cs.meta b/com.unity.netcode.gameobjects/Runtime/Logging/GenericContext.cs.meta new file mode 100644 index 0000000000..ee66b3749f --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Logging/GenericContext.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3ed903e3a2894bf1980e589f98e16be9 +timeCreated: 1776401645 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs new file mode 100644 index 0000000000..d3a327030c --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs @@ -0,0 +1,78 @@ +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace Unity.Netcode +{ + internal interface ILogContext + { + public void AppendTo(ContextBuilder builder) + { + } + } + + internal struct Context : ILogContext + { + public readonly LogLevel Level; + private readonly string m_CallingFunction; + internal readonly string Message; + public GameObject GameObjectOverride; + + + private readonly GenericContext m_Other; + + public Context(LogLevel level, string msg, [CallerMemberName] string memberName = "") + { + Level = level; + Message = msg; + m_CallingFunction = memberName; + + m_Other = GenericContext.Create(); + GameObjectOverride = null; + } + + internal Context(LogLevel level, string msg, bool noCaller) + { + Level = level; + Message = msg; + m_CallingFunction = null; + + m_Other = GenericContext.Create(); + GameObjectOverride = null; + } + + public void AppendTo(ContextBuilder builder) + { + // [CallingFunction] + if (!string.IsNullOrEmpty(m_CallingFunction)) + { + builder.AppendContext(m_CallingFunction); + } + + // [SomeContext][SomeName:SomeValue] + m_Other.AppendTo(builder); + + // Human-readable log message + builder.Append(" "); + builder.Append(Message); + } + + public Context With(object key, object value) + { + m_Other.StoreInfo(key, value); + return this; + } + + public Context With(string msg) + { + m_Other.StoreContext(msg); + return this; + } + + public Context ForNetworkPrefab(NetworkPrefab networkPrefab) + { + GameObjectOverride = networkPrefab.Prefab.gameObject; + m_Other.StoreInfo(nameof(NetworkPrefab), networkPrefab.Prefab.name); + return this; + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs.meta b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs.meta new file mode 100644 index 0000000000..a2a4e87ebd --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a0c75597200c497996b677d385e3a8d4 +timeCreated: 1776401476 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/LogContextNetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Logging/LogContextNetworkManager.cs new file mode 100644 index 0000000000..57e274417f --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Logging/LogContextNetworkManager.cs @@ -0,0 +1,110 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using UnityEngine; + +namespace Unity.Netcode +{ + internal struct LogContextNetworkManager : ILogContext, IDisposable + { + private NetworkManager m_NetworkManager; + private const string k_ServerString = "[Server]"; + private const string k_SessionOwnerString = "[Session Owner]"; + // This will get appended with the current clientID, doesn't need brackets + private const string k_ClientString = "Client"; + + public LogContextNetworkManager(bool useSingleton) + { + m_NetworkManager = null; + if (!useSingleton) + { + return; + } + + WatchForSingleton(); + } + + public LogContextNetworkManager([NotNull] NetworkManager networkManager) + { + m_NetworkManager = networkManager; + } + + public readonly LogLevel LogLevel => m_NetworkManager?.LogLevel ?? LogLevel.Normal; + + public readonly void TrySendMessage(LogType logType, string message) + { + if (m_NetworkManager != null + && m_NetworkManager.IsListening + && (m_NetworkManager?.NetworkConfig.EnableNetworkLogs ?? false) + && (m_NetworkManager.IsServer || m_NetworkManager.LocalClient.IsSessionOwner)) + { + var messageType = NetworkLog.GetMessageLogType(logType); + NetworkLog.SendLogToAuthority(m_NetworkManager, messageType, m_NetworkManager.LocalClientId, message); + } + } + + private void WatchForSingleton() + { + if (NetworkManager.Singleton != null) + { + m_NetworkManager = NetworkManager.Singleton; + NetworkManager.OnDestroying += OnManagerDestroying; + } + else + { + NetworkManager.OnSingletonReady += OnSingletonReady; + } + } + + private void OnSingletonReady() + { + m_NetworkManager = NetworkManager.Singleton; + NetworkManager.OnSingletonReady -= OnSingletonReady; + } + + private void OnManagerDestroying(NetworkManager manager) + { + if (m_NetworkManager != manager) + { + return; + } + m_NetworkManager = null; + WatchForSingleton(); + } + + public readonly void AppendTo(ContextBuilder builder) + { + if (m_NetworkManager == null) + { + return; + } + + if (!m_NetworkManager.IsListening || !NetworkLog.Config.LogNetworkManagerRole) + { + return; + } + + if (m_NetworkManager.LocalClient.IsSessionOwner) + { + // [Session Owner] + builder.Append(k_SessionOwnerString); + } else if (m_NetworkManager.IsServer) + { + // [Server] + builder.Append(k_ServerString); + } + if (!m_NetworkManager.IsServer) + { + // [Client:1] + builder.AppendContext(k_ClientString, m_NetworkManager.LocalClientId); + } + } + + public void Dispose() + { + NetworkManager.OnDestroying -= OnManagerDestroying; + NetworkManager.OnSingletonReady -= OnSingletonReady; + m_NetworkManager = null; + } + } + +} diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/LogContextNetworkManager.cs.meta b/com.unity.netcode.gameobjects/Runtime/Logging/LogContextNetworkManager.cs.meta new file mode 100644 index 0000000000..f23412129f --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Logging/LogContextNetworkManager.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ad18865d062a4e80bcf3636e613a631b +timeCreated: 1776379064 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs b/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs index 59557cc81a..791045c1fc 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs @@ -1,139 +1,128 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; using UnityEngine; namespace Unity.Netcode { + internal struct LogConfiguration + { + internal bool LogNetworkManagerRole; + } + /// /// Helper class for logging /// public static class NetworkLog { + private static readonly ContextualLogger k_Log = new(true); + + internal static void SetNetworkManager(NetworkManager networkManager) + { + k_Log.UpdateNetworkManagerContext(networkManager); + } /// /// Gets the current log level. /// /// The current log level. + // [Obsolete("Use the LogLevel directly on the NetworkManager instead")] public static LogLevel CurrentLogLevel => NetworkManager.Singleton == null ? LogLevel.Normal : NetworkManager.Singleton.LogLevel; + internal static LogConfiguration Config = new LogConfiguration(); + // internal logging /// /// Locally logs a info log with Netcode prefixing. /// /// The message to log - public static void LogInfo(string message) => Debug.Log($"[Netcode] {message}"); + [HideInCallstack] + public static void LogInfo(string message, [CallerMemberName] string memberName = "") => k_Log.Info(new Context(LogLevel.Normal, message, memberName)); + [HideInCallstack] + internal static void LogInfo(Context context) => k_Log.Info(context); /// /// Locally logs a warning log with Netcode prefixing. /// /// The message to log - public static void LogWarning(string message) => Debug.LogWarning($"[Netcode] {message}"); + [HideInCallstack] + public static void LogWarning(string message, [CallerMemberName] string memberName = "") => k_Log.Warning(new Context(LogLevel.Error, message, memberName)); + [HideInCallstack] + internal static void LogWarning(Context context) => k_Log.Warning(context); /// /// Locally logs a error log with Netcode prefixing. /// /// The message to log - public static void LogError(string message) => Debug.LogError($"[Netcode] {message}"); + [HideInCallstack] + public static void LogError(string message, [CallerMemberName] string memberName = "") => k_Log.Error(new Context(LogLevel.Error, message, memberName)); + [HideInCallstack] + internal static void LogError(Context context) => k_Log.Error(context); + + // internal static void Log(LogLevel level, object message, Object gameObject) => Logger.Log($"[Netcode] {message} ({(int)level})"); /// /// Logs an info log locally and on the server if possible. /// /// The message to log - public static void LogInfoServer(string message) => LogServer(message, LogType.Info); + [HideInCallstack] + public static void LogInfoServer(string message, [CallerMemberName] string memberName = "") => k_Log.InfoServer(new Context(LogLevel.Normal, message, memberName)); /// /// Logs an info log locally and on the session owner if possible. /// /// The message to log - public static void LogInfoSessionOwner(string message) => LogServer(message, LogType.Info); + [HideInCallstack] + public static void LogInfoSessionOwner(string message, [CallerMemberName] string memberName = "") => k_Log.InfoServer(new Context(LogLevel.Normal, message, memberName)); /// /// Logs a warning log locally and on the server if possible. /// /// The message to log - public static void LogWarningServer(string message) => LogServer(message, LogType.Warning); + [HideInCallstack] + public static void LogWarningServer(string message, [CallerMemberName] string memberName = "") => k_Log.WarningServer(new Context(LogLevel.Error, message, memberName)); /// /// Logs an error log locally and on the server if possible. /// /// The message to log - public static void LogErrorServer(string message) => LogServer(message, LogType.Error); - - internal static NetworkManager NetworkManagerOverride; + [HideInCallstack] + public static void LogErrorServer(string message, [CallerMemberName] string memberName = "") => k_Log.ErrorServer(new Context(LogLevel.Error, message, memberName)); - private static void LogServer(string message, LogType logType) + internal static LogType GetMessageLogType(UnityEngine.LogType engineLogType) { - var networkManager = NetworkManagerOverride ??= NetworkManager.Singleton; - // Get the sender of the local log - ulong localId = networkManager?.LocalClientId ?? 0; - bool isServer = networkManager && networkManager.DistributedAuthorityMode ? networkManager.LocalClient.IsSessionOwner : - networkManager && !networkManager.DistributedAuthorityMode ? networkManager.IsServer : true; - switch (logType) + return engineLogType switch { - case LogType.Info: - if (isServer) - { - LogInfoServerLocal(message, localId); - } - else - { - LogInfo(message); - } - break; - case LogType.Warning: - if (isServer) - { - LogWarningServerLocal(message, localId); - } - else - { - LogWarning(message); - } - break; - case LogType.Error: - if (isServer) - { - LogErrorServerLocal(message, localId); - } - else - { - LogError(message); - } - break; - } + UnityEngine.LogType.Error => LogType.Error, + UnityEngine.LogType.Warning => LogType.Warning, + UnityEngine.LogType.Log => LogType.Info, + _ => LogType.None + }; + } - if (!isServer && networkManager.NetworkConfig.EnableNetworkLogs) + internal static void SendLogToAuthority(NetworkManager networkManager, LogType logType, ulong senderId, string message) + { + var networkMessage = new ServerLogMessage { - var networkMessage = new ServerLogMessage - { - LogType = logType, - Message = message, - SenderId = localId - }; - var size = networkManager.ConnectionManager.SendMessage(ref networkMessage, MessageDeliveryType.DefaultDelivery, NetworkManager.ServerClientId); - networkManager.NetworkMetrics.TrackServerLogSent(NetworkManager.ServerClientId, (uint)logType, size); - } + LogType = logType, + Message = message, + SenderId = senderId + }; + var size = networkManager.ConnectionManager.SendMessage(ref networkMessage, MessageDeliveryType.DefaultDelivery, NetworkManager.ServerClientId); + networkManager.NetworkMetrics.TrackServerLogSent(NetworkManager.ServerClientId, (uint)logType, size); } - private const string k_HeaderStart = "Netcode"; - private static string Header() + private const string k_SenderId = "SenderId"; + internal static Context ContextWithSenderId([NotNull] NetworkManager networkManager, LogLevel level, ulong senderId, string message) { - var networkManager = NetworkManagerOverride ??= NetworkManager.Singleton; - if (networkManager != null) + var ctx = new Context(level, message, true).With(k_SenderId, senderId); + if (TryGetNetworkObjectName(networkManager, message, out var name)) { - if (networkManager.DistributedAuthorityMode) - { - return $"{k_HeaderStart}-Session-Owner"; - } - return $"{k_HeaderStart}-Server"; + ctx.With(name); } - - // If NetworkManager no longer exists, then return the generic header - return k_HeaderStart; + return ctx; } - internal static void LogInfoServerLocal(string message, ulong sender) => Debug.Log($"[{Header()} Sender={sender}] {message}"); - internal static void LogWarningServerLocal(string message, ulong sender) => Debug.LogWarning($"[{Header()} Sender={sender}] {message}"); - internal static void LogErrorServerLocal(string message, ulong sender) => Debug.LogError($"[{Header()} Sender={sender}] {message}"); - internal enum LogType : byte { Info, @@ -141,5 +130,31 @@ internal enum LogType : byte Error, None } + + private static readonly Regex k_GlobalObjectIdHash = new($@"\[{nameof(NetworkObject.GlobalObjectIdHash)}=(\d+)\]", RegexOptions.Compiled); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool TryGetNetworkObjectName([NotNull] NetworkManager networkManager, string message, out string name) + { + name = null; + if (!k_GlobalObjectIdHash.IsMatch(message)) + { + return false; + } + + var stringHash = k_GlobalObjectIdHash.Match(message).Groups[1].Value; + if (!ulong.TryParse(stringHash, out var globalObjectIdHash)) + { + return false; + } + + if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(globalObjectIdHash, out var networkObject)) + { + return false; + } + + name = networkObject.name; + return true; + } + } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs index 23f32a608f..cc6be184ea 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs @@ -46,20 +46,24 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; - var senderId = networkManager.DistributedAuthorityMode ? SenderId : context.SenderId; + var senderId = context.SenderId; + if (networkManager.NetworkConfig.UseCMBService && context.SenderId == NetworkManager.ServerClientId) + { + senderId = SenderId; + } networkManager.NetworkMetrics.TrackServerLogReceived(senderId, (uint)LogType, context.MessageSize); switch (LogType) { case NetworkLog.LogType.Info: - NetworkLog.LogInfoServerLocal(Message, senderId); + networkManager.Log.Info(NetworkLog.ContextWithSenderId(networkManager, LogLevel.Normal, senderId, Message)); break; case NetworkLog.LogType.Warning: - NetworkLog.LogWarningServerLocal(Message, senderId); + networkManager.Log.Warning(NetworkLog.ContextWithSenderId(networkManager, LogLevel.Error, senderId, Message)); break; case NetworkLog.LogType.Error: - NetworkLog.LogErrorServerLocal(Message, senderId); + networkManager.Log.Error(NetworkLog.ContextWithSenderId(networkManager, LogLevel.Error, senderId, Message)); break; } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs index 9f2baa4113..18c5b4554f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs @@ -152,7 +152,7 @@ public IEnumerator TestNetworkObjectClientDestroy([Values] ClientDestroyObject c // The non-authority client is =NOT= allowed to destroy any spawned object it does not // have authority over during runtime. LogAssert.ignoreFailingMessages = true; - NetworkLog.NetworkManagerOverride = nonAuthorityClient; + NetworkLog.SetNetworkManager(nonAuthorityClient); Object.Destroy(clientPlayerClone.gameObject); } @@ -201,12 +201,6 @@ private bool HaveLogsBeenReceived() return true; } - protected override IEnumerator OnTearDown() - { - NetworkLog.NetworkManagerOverride = null; - return base.OnTearDown(); - } - protected override void OnOneTimeTearDown() { // Re-apply the default as the last exiting action diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs index f747bbbd5d..7da34dc860 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs @@ -594,6 +594,7 @@ private void InternalOnOneTimeSetup() IntegrationTestSceneHandler.VerboseDebugMode = m_EnableVerboseDebug; NetworkManagerHelper.VerboseDebugMode = m_EnableVerboseDebug; VerboseDebug($"Entering {nameof(OneTimeSetup)}"); + // NetworkLog.Config.LogNetworkManagerRole = true; m_NetworkManagerInstatiationMode = OnSetIntegrationTestMode(); @@ -808,7 +809,7 @@ protected void CreateServerAndClients(int numberOfClients) m_NumberOfClients = numberOfClients; m_ClientNetworkManagers = clients; m_ServerNetworkManager = server; - NetworkLog.NetworkManagerOverride = server; + NetworkLog.SetNetworkManager(server); var managers = clients.ToList(); if (!m_UseCmbService) From f5967dc38cd398b20a55873adcf200c279fd99a7 Mon Sep 17 00:00:00 2001 From: Emma Date: Fri, 17 Apr 2026 13:20:25 -0400 Subject: [PATCH 02/16] Use new logger for the NetworkManager --- .../Editor/NetworkManagerEditor.cs | 2 +- .../Editor/NetworkManagerHelper.cs | 12 +- .../Runtime/Core/NetworkManager.cs | 149 ++++++++---------- .../NetworkManagerConfigurationTests.cs | 8 +- 4 files changed, 76 insertions(+), 95 deletions(-) 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/Tests/Editor/NetworkManagerConfigurationTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs index d3dd92de2d..d453e1a917 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; +using System.Text.RegularExpressions; using NUnit.Framework; using Unity.Netcode.Editor; +using Unity.Netcode.RuntimeTests; using Unity.Netcode.Transports.UTP; using UnityEditor.SceneManagement; using UnityEngine; @@ -42,7 +44,7 @@ public void NestedNetworkManagerCheck() var messageToCheck = NetworkManager.GenerateNestedNetworkManagerMessage(networkManagerObject.transform); // Trap for the nested NetworkManager exception - LogAssert.Expect(LogType.Error, messageToCheck); + LogAssert.Expect(LogType.Error, new Regex(messageToCheck)); // Since this is an in-editor test, we must force this invocation NetworkManagerHelper.Singleton.NotifyUserOfNestedNetworkManager(networkManager, false, true); @@ -73,7 +75,7 @@ public void NetworkObjectNotAllowed([Values] NetworkObjectPlacement networkObjec var networkManager = gameObject.AddComponent(); // Trap for the error message generated when a NetworkObject is discovered on the same GameObject or any children under it - LogAssert.Expect(LogType.Error, NetworkManagerHelper.Singleton.NetworkManagerAndNetworkObjectNotAllowedMessage()); + LogAssert.Expect(LogType.Error, new Regex(NetworkManagerHelper.Singleton.NetworkManagerAndNetworkObjectNotAllowedMessage())); // Add the NetworkObject var networkObject = targetforNetworkObject.AddComponent(); @@ -119,7 +121,7 @@ public void NestedNetworkObjectPrefabCheck() networkManager.OnValidate(); // Expect a warning - LogAssert.Expect(LogType.Warning, $"[Netcode] {NetworkPrefabHandler.PrefabDebugHelper(networkManager.NetworkConfig.Prefabs.Prefabs[0])} has child {nameof(NetworkObject)}(s) but they will not be spawned across the network (unsupported {nameof(NetworkPrefab)} setup)"); + LogAssert.Expect(LogType.Warning, new Regex($"{parent.name}\\] Prefab has child {nameof(NetworkObject)}\\(s\\) but they will not be spawned across the network \\(unsupported {nameof(NetworkPrefab)} setup\\)")); // Clean up Object.DestroyImmediate(networkManagerObject); From 0a001cade7433d46deb1ccd6546a2355d9a5ba35 Mon Sep 17 00:00:00 2001 From: Emma Date: Thu, 23 Apr 2026 14:08:16 -0400 Subject: [PATCH 03/16] Update naming and move to a Logging namespace --- .../Editor/NetworkManagerEditor.cs | 3 +- .../Editor/NetworkManagerHelper.cs | 1 + .../Runtime/Core/NetworkManager.cs | 63 +++-------- .../Runtime/Logging/ContextualLogger.cs | 104 ++++++++++-------- .../Runtime/Logging/GenericContext.cs | 10 +- .../Runtime/Logging/LogBuilder.cs | 37 +++++++ .../Runtime/Logging/LogBuilder.cs.meta | 3 + .../Runtime/Logging/LogContext.cs | 28 ++--- .../Logging/LogContextNetworkManager.cs | 6 +- .../Runtime/Logging/NetworkLog.cs | 5 +- .../NetworkManagerConfigurationTests.cs | 21 ++-- 11 files changed, 152 insertions(+), 129 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Runtime/Logging/LogBuilder.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Logging/LogBuilder.cs.meta diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs index 293836d22d..a4e339f558 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using Unity.Netcode.Editor.Configuration; +using Unity.Netcode.Logging; using UnityEditor; using UnityEngine; #if UNITY_6000_5_OR_NEWER @@ -298,7 +299,7 @@ private void DisplayNetworkManagerProperties() #else string path = Path.Combine(directory, $"NetworkPrefabs-{m_NetworkManager.GetInstanceID()}.asset"); #endif - m_NetworkManager.Log.Info(new Context(LogLevel.Normal, "Saving migrated Network Prefabs List").With("Path", path)); + m_NetworkManager.Log.Info(new Context(LogLevel.Normal, "Saving migrated Network Prefabs List").AddInfo("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 4352749f5f..5c7e554baf 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using Unity.Netcode.Editor.Configuration; +using Unity.Netcode.Logging; using UnityEditor; using UnityEngine; using UnityEngine.SceneManagement; diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 14ee04ea0b..9969fcde75 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -3,6 +3,7 @@ using Unity.Collections; using System.Linq; using Unity.Netcode.Components; +using Unity.Netcode.Logging; using Unity.Netcode.Runtime; using UnityEngine; #if UNITY_EDITOR @@ -73,7 +74,7 @@ internal static void LogSerializedTypeNotOptimized() if (!s_SerializedType.Contains(type)) { s_SerializedType.Add(type); - NetworkLog.LogWarning(new Context(LogLevel.Developer, "Serialized type has not been optimized for use with Distributed Authority!").With(type.Name)); + NetworkLog.LogWarning(new Context(LogLevel.Developer, "Serialized type has not been optimized for use with Distributed Authority!").AddTag(type.Name)); } } #endif @@ -242,12 +243,12 @@ internal void PromoteSessionOwner(ulong clientId) if (!DistributedAuthorityMode) { // [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")); + Log.ErrorServer(new Context(LogLevel.Error, "Invoking promote session owner while not in distributed authority mode!").AddTag("SceneManagement").AddTag("NotDA")); return; } if (!DAHost) { - Log.ErrorServer(new Context(LogLevel.Error, "Client is attempting to promote another client as the session owner!").With("SceneManagement").With("NotDAHost")); + Log.ErrorServer(new Context(LogLevel.Error, "Client is attempting to promote another client as the session owner!").AddTag("SceneManagement").AddTag("NotDAHost")); return; } SetSessionOwner(clientId); @@ -315,7 +316,7 @@ private void UpdateTopology() if (transportTopology != NetworkConfig.NetworkTopology) { 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)); + .AddTag("Topology Mismatch").AddInfo(transportTopology, transportTopology.GetType().Name).AddInfo("NetworkManager.NetworkConfig", NetworkConfig.NetworkTopology)); Shutdown(true); } else @@ -1054,10 +1055,10 @@ private void Awake() { if (Log == null) { - Log = new ContextualLogger(this, gameObject); + Log = new ContextualLogger(this, this); } - NetworkConfig?.InitializePrefabs(); + NetworkConfig?.InitializePrefabs(Log); UnityEngine.SceneManagement.SceneManager.sceneUnloaded += OnSceneUnloaded; #if UNITY_EDITOR @@ -1263,7 +1264,7 @@ internal void Initialize(bool server) BehaviourUpdater = new NetworkBehaviourUpdater(); BehaviourUpdater.Initialize(this); - NetworkConfig.InitializePrefabs(); + NetworkConfig.InitializePrefabs(Log); PrefabHandler.RegisterPlayerPrefab(); #if UNITY_EDITOR BeginNetworkSession(); @@ -1285,7 +1286,7 @@ private bool CanStart(StartType type) { if (IsListening) { - Log.Warning(new Context(LogLevel.Normal, "Can't start while listening").With("Start", type)); + Log.Warning(new Context(LogLevel.Normal, "Can't start while listening").AddInfo("Start", type)); return false; } @@ -1295,7 +1296,7 @@ private bool CanStart(StartType type) { if (ConnectionApprovalCallback == null) { - Log.Warning(new Context(LogLevel.Normal, $"No {nameof(ConnectionApprovalCallback)} defined. Connection approval will timeout").With("Start", type)); + Log.Warning(new Context(LogLevel.Normal, $"No {nameof(ConnectionApprovalCallback)} defined. Connection approval will timeout").AddInfo("Start", type)); } } @@ -1303,7 +1304,7 @@ private bool CanStart(StartType type) { if (!NetworkConfig.ConnectionApproval) { - 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)); + 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").AddInfo("Start", type)); } } @@ -1725,7 +1726,7 @@ private void OnApplicationQuit() #if UNITY_EDITOR if (Singleton != null) { - 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)); + 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?").AddInfo("SingletonInstance", Singleton.name)); } #endif } @@ -1824,7 +1825,7 @@ internal void OnValidate() if (Log == null) { - Log = new ContextualLogger(this, gameObject); + Log = new ContextualLogger(this, this); } // Do a validation pass on NetworkConfig properties @@ -1835,44 +1836,6 @@ internal void OnValidate() Log.Warning(new Context(LogLevel.Normal, $"{nameof(NetworkManager)} cannot be a {nameof(NetworkObject)}.")); } - var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene(); - - // If the scene is not dirty or the asset database is currently updating then we can skip updating the NetworkPrefab information - if (!activeScene.isDirty || EditorApplication.isUpdating) - { - return; - } - - // During OnValidate we will always clear out NetworkPrefabOverrideLinks and rebuild it - NetworkConfig.Prefabs.NetworkPrefabOverrideLinks.Clear(); - - var prefabs = NetworkConfig.Prefabs.Prefabs; - // Check network prefabs and assign to dictionary for quick look up - foreach (var networkPrefab in prefabs) - { - var networkPrefabGo = networkPrefab?.Prefab; - if (networkPrefabGo == null) - { - continue; - } - - var networkObject = networkPrefabGo.GetComponent(); - if (networkObject == null) - { - Log.Warning(new Context(LogLevel.Normal, $"Cannot register prefab to {nameof(NetworkManager)}, missing a {nameof(NetworkObject)} component at its root").ForNetworkPrefab(networkPrefab)); - continue; - } - - { - var childNetworkObjects = new List(); - networkPrefabGo.GetComponentsInChildren(true, childNetworkObjects); - if (childNetworkObjects.Count > 1) // total count = 1 root NetworkObject + n child NetworkObjects - { - 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)); - } - } - } - try { OnValidateComponent(); diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs b/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs index f5a671ce07..6999394066 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs @@ -2,37 +2,55 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -using System.Text; using UnityEngine; using Debug = UnityEngine.Debug; using LogType = UnityEngine.LogType; +using Object = UnityEngine.Object; -namespace Unity.Netcode +namespace Unity.Netcode.Logging { + /// + /// Configurable structured logger. + /// Each logger instance collects system-wide context + /// (e.g. which to attribute the logs to; or the relating to this object) + /// Each log is made with a object that collects the local context of this individual log line + /// The contextual logger will combine the system-wide context and the local context into one structured log message. + /// 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 readonly bool m_UseCompatibilityMode; + private readonly Object m_Object; + private readonly LogBuilder m_Builder = new(); private LogContextNetworkManager m_ManagerContext; private readonly GenericContext m_LoggerContext; private const string k_CompilationCondition = "UNITY_ASSERTIONS"; + /// + /// Creates a minimally configured contextual logger + /// + /// Suppresses adding public ContextualLogger(bool useCompatibilityMode = false) { m_UseCompatibilityMode = useCompatibilityMode; m_ManagerContext = new LogContextNetworkManager(true); - m_GameObject = null; + m_Object = null; m_LoggerContext = GenericContext.Create(); } - public ContextualLogger([NotNull] NetworkManager networkManager, GameObject gameObject) + public ContextualLogger(Object inspectorObject) + { + m_ManagerContext = new LogContextNetworkManager(true); + m_Object = inspectorObject; + m_LoggerContext = GenericContext.Create(); + } + + public ContextualLogger(Object inspectorObject, [NotNull] NetworkManager networkManager) { m_ManagerContext = new LogContextNetworkManager(networkManager); - m_GameObject = gameObject; + m_Object = inspectorObject; m_LoggerContext = GenericContext.Create(); } @@ -44,24 +62,29 @@ internal void UpdateNetworkManagerContext(NetworkManager manager) } [Conditional(k_CompilationCondition)] - internal void PushContext(string key, object value) + internal void AddInfo(string key, object value) { m_LoggerContext.StoreInfo(key, value); } - [Conditional(k_CompilationCondition)] - internal void PushContext(string key) + /// + /// Adds info onto a logger that will be removed once the is disposed + /// + /// Key to log + /// Value to log + /// Object to dispose when context is no longer valid + internal DisposableContext AddDisposableInfo(string key, object value) { - m_LoggerContext.StoreContext(key); + m_LoggerContext.StoreInfo(key, value); + return new DisposableContext(this, key); } [Conditional(k_CompilationCondition)] - internal void PopContext(string key) + internal void RemoveInfo(string key) { m_LoggerContext.ClearInfo(key); } - [HideInCallstack] [Conditional(k_CompilationCondition)] [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -93,7 +116,7 @@ public void CaptureFunctionCall([CallerMemberName] string memberName = "") [HideInCallstack] public void Exception(Exception exception) { - Debug.unityLogger.LogException(exception, m_GameObject); + Debug.unityLogger.LogException(exception, m_Object); } [HideInCallstack] @@ -106,7 +129,7 @@ private void Log(LogType logType, Context context) } var message = BuildLog(context); - Debug.unityLogger.Log(logType, (object)message, context.GameObjectOverride ?? m_GameObject); + Debug.unityLogger.Log(logType, (object)message, context.RelevantObjectOverride ?? m_Object); } [HideInCallstack] @@ -119,9 +142,9 @@ private void LogServer(LogType logType, Context context) } var message = BuildLog(context); - Debug.unityLogger.Log(logType, (object)message, context.GameObjectOverride ?? m_GameObject); + Debug.unityLogger.Log(logType, (object)message, context.RelevantObjectOverride ?? m_Object); - m_ManagerContext.TrySendMessage(logType, message); + m_ManagerContext.TrySendMessage(logType, message.Remove(0, k_NetcodeHeader.Length)); } private string BuildLog(Context context) @@ -132,7 +155,7 @@ private string BuildLog(Context context) m_Builder.Append(k_NetcodeHeader); if (m_UseCompatibilityMode) - { + { ; m_Builder.Append(context.Message); } else @@ -147,38 +170,25 @@ private string BuildLog(Context context) 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() + /// + /// Removes the configured context from the logger when this object is disposed. + /// + public readonly struct DisposableContext: IDisposable { - m_Builder.Clear(); - } + private readonly ContextualLogger m_Logger; + private readonly string m_ToClear; - public void AppendContext(string context) - { - m_Builder.Append(k_OpenBracket); - m_Builder.Append(context); - m_Builder.Append(k_CloseBracket); - } + internal DisposableContext(ContextualLogger logger, string toClear) + { + m_Logger = logger; + m_ToClear = toClear; + } - 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 Dispose() + { + m_Logger.RemoveInfo(m_ToClear); + } } - - public void Append(string value) => m_Builder.Append(value); - - public string Build() => m_Builder.ToString(); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/GenericContext.cs b/com.unity.netcode.gameobjects/Runtime/Logging/GenericContext.cs index 042d78e90c..47d198f447 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/GenericContext.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/GenericContext.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace Unity.Netcode +namespace Unity.Netcode.Logging { internal readonly struct GenericContext : ILogContext, IDisposable { @@ -14,13 +14,13 @@ private GenericContext(List contexts, Dictionary info) m_Info = info; } - public readonly void AppendTo(ContextBuilder builder) + public void AppendTo(LogBuilder builder) { if (m_Contexts != null) { foreach (var ctx in m_Contexts) { - builder.AppendContext(ctx); + builder.AppendTag(ctx); } } @@ -28,12 +28,12 @@ public readonly void AppendTo(ContextBuilder builder) { foreach (var (key, value) in m_Info) { - builder.AppendContext(key, value); + builder.AppendInfo(key, value); } } } - public void StoreContext(string msg) + public void StoreTag(string msg) { m_Contexts.Add(msg); } diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/LogBuilder.cs b/com.unity.netcode.gameobjects/Runtime/Logging/LogBuilder.cs new file mode 100644 index 0000000000..0c59e10bd4 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Logging/LogBuilder.cs @@ -0,0 +1,37 @@ +using System.Text; + +namespace Unity.Netcode +{ + internal class LogBuilder + { + 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 AppendTag(string context) + { + m_Builder.Append(k_OpenBracket); + m_Builder.Append(context); + m_Builder.Append(k_CloseBracket); + } + + public void AppendInfo(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/LogBuilder.cs.meta b/com.unity.netcode.gameobjects/Runtime/Logging/LogBuilder.cs.meta new file mode 100644 index 0000000000..66a2203951 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Logging/LogBuilder.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1201e32badad4870be469c4a4c5d9359 +timeCreated: 1776803724 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs index d3a327030c..c40ca6696f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs @@ -1,11 +1,13 @@ using System.Runtime.CompilerServices; +using System.Text; using UnityEngine; +using Object = UnityEngine.Object; -namespace Unity.Netcode +namespace Unity.Netcode.Logging { internal interface ILogContext { - public void AppendTo(ContextBuilder builder) + public void AppendTo(LogBuilder builder) { } } @@ -15,8 +17,7 @@ internal struct Context : ILogContext public readonly LogLevel Level; private readonly string m_CallingFunction; internal readonly string Message; - public GameObject GameObjectOverride; - + internal Object RelevantObjectOverride; private readonly GenericContext m_Other; @@ -27,7 +28,7 @@ public Context(LogLevel level, string msg, [CallerMemberName] string memberName m_CallingFunction = memberName; m_Other = GenericContext.Create(); - GameObjectOverride = null; + RelevantObjectOverride = null; } internal Context(LogLevel level, string msg, bool noCaller) @@ -37,15 +38,15 @@ internal Context(LogLevel level, string msg, bool noCaller) m_CallingFunction = null; m_Other = GenericContext.Create(); - GameObjectOverride = null; + RelevantObjectOverride = null; } - public void AppendTo(ContextBuilder builder) + public void AppendTo(LogBuilder builder) { // [CallingFunction] if (!string.IsNullOrEmpty(m_CallingFunction)) { - builder.AppendContext(m_CallingFunction); + builder.AppendTag(m_CallingFunction); } // [SomeContext][SomeName:SomeValue] @@ -56,22 +57,21 @@ public void AppendTo(ContextBuilder builder) builder.Append(Message); } - public Context With(object key, object value) + public Context AddInfo(object key, object value) { m_Other.StoreInfo(key, value); return this; } - public Context With(string msg) + public Context AddTag(string msg) { - m_Other.StoreContext(msg); + m_Other.StoreTag(msg); return this; } - public Context ForNetworkPrefab(NetworkPrefab networkPrefab) + public Context ForGameObject(GameObject prefabObj) { - GameObjectOverride = networkPrefab.Prefab.gameObject; - m_Other.StoreInfo(nameof(NetworkPrefab), networkPrefab.Prefab.name); + RelevantObjectOverride = prefabObj.gameObject; return this; } } diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/LogContextNetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Logging/LogContextNetworkManager.cs index 57e274417f..2a8075e3df 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/LogContextNetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/LogContextNetworkManager.cs @@ -2,7 +2,7 @@ using System.Diagnostics.CodeAnalysis; using UnityEngine; -namespace Unity.Netcode +namespace Unity.Netcode.Logging { internal struct LogContextNetworkManager : ILogContext, IDisposable { @@ -71,7 +71,7 @@ private void OnManagerDestroying(NetworkManager manager) WatchForSingleton(); } - public readonly void AppendTo(ContextBuilder builder) + public readonly void AppendTo(LogBuilder builder) { if (m_NetworkManager == null) { @@ -95,7 +95,7 @@ public readonly void AppendTo(ContextBuilder builder) if (!m_NetworkManager.IsServer) { // [Client:1] - builder.AppendContext(k_ClientString, m_NetworkManager.LocalClientId); + builder.AppendInfo(k_ClientString, m_NetworkManager.LocalClientId); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs b/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs index 791045c1fc..2cd13fccf0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; +using Unity.Netcode.Logging; using UnityEngine; namespace Unity.Netcode @@ -115,10 +116,10 @@ internal static void SendLogToAuthority(NetworkManager networkManager, LogType l private const string k_SenderId = "SenderId"; internal static Context ContextWithSenderId([NotNull] NetworkManager networkManager, LogLevel level, ulong senderId, string message) { - var ctx = new Context(level, message, true).With(k_SenderId, senderId); + var ctx = new Context(level, message, true).AddInfo(k_SenderId, senderId); if (TryGetNetworkObjectName(networkManager, message, out var name)) { - ctx.With(name); + ctx.AddTag(name); } return ctx; } diff --git a/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs index d453e1a917..41d300ef50 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs @@ -2,6 +2,7 @@ using System.Text.RegularExpressions; using NUnit.Framework; using Unity.Netcode.Editor; +using Unity.Netcode.Logging; using Unity.Netcode.RuntimeTests; using Unity.Netcode.Transports.UTP; using UnityEditor.SceneManagement; @@ -121,7 +122,7 @@ public void NestedNetworkObjectPrefabCheck() networkManager.OnValidate(); // Expect a warning - LogAssert.Expect(LogType.Warning, new Regex($"{parent.name}\\] Prefab has child {nameof(NetworkObject)}\\(s\\) but they will not be spawned across the network \\(unsupported {nameof(NetworkPrefab)} setup\\)")); + LogAssert.Expect(LogType.Warning, new Regex($@"{parent.name}\] Prefab has child {nameof(NetworkObject)}\(s\) but they will not be spawned across the network \(unsupported {nameof(NetworkPrefab)} setup\)")); // Clean up Object.DestroyImmediate(networkManagerObject); @@ -150,7 +151,8 @@ public void WhenNetworkConfigContainsOldPrefabList_TheyMigrateProperlyToTheNewLi new NetworkPrefab { Prefab = overriddenPrefab.gameObject, Override = NetworkPrefabOverride.Prefab, OverridingTargetPrefab = overridingTargetPrefab.gameObject, SourcePrefabToOverride = sourcePrefabToOverride.gameObject, SourceHashToOverride = 123456 } }; - networkConfig.InitializePrefabs(); + var log = new ContextualLogger(); + networkConfig.InitializePrefabs(log); Assert.IsNull(networkConfig.OldPrefabList); Assert.IsNotNull(networkConfig.Prefabs); @@ -300,14 +302,19 @@ public void WhenModifyingPrefabListUsingPrefabsAPI_ModificationIsLocal() public void WhenThereAreUninitializedElementsInPrefabsList_NoErrors() { var networkConfig = new NetworkConfig(); + var listWithNull = ScriptableObject.CreateInstance(); + listWithNull.List.Add(new NetworkPrefab { Prefab = null }); + listWithNull.List.Add(null); - networkConfig.Prefabs.NetworkPrefabsLists = new List { null }; + networkConfig.Prefabs.NetworkPrefabsLists = new List { null, listWithNull }; - networkConfig.InitializePrefabs(); + Assert.That(networkConfig.Prefabs.NetworkPrefabsLists.Count, Is.EqualTo(2), "Failed test setup: Both the null element and the list containing a null Prefab should be listed"); - // Null elements will be removed from the list so it should be empty - Assert.IsTrue(networkConfig.Prefabs.NetworkPrefabsLists.Count == 0); - Assert.IsTrue(networkConfig.Prefabs.Prefabs.Count == 0); + var log = new ContextualLogger(); + networkConfig.InitializePrefabs(log); + + Assert.That(networkConfig.Prefabs.NetworkPrefabsLists.Count, Is.EqualTo(1), "null element should have been removed"); + Assert.That(networkConfig.Prefabs.Prefabs.Count, Is.EqualTo(0), "Invalid prefab was registered"); networkConfig.Prefabs.Shutdown(); } From c805d07df7d4d7efd49398536ab93bf7e611cf85 Mon Sep 17 00:00:00 2001 From: Emma Date: Thu, 23 Apr 2026 14:28:32 -0400 Subject: [PATCH 04/16] Small fixes --- .../Runtime/Core/NetworkManager.cs | 1 - .../Runtime/Logging/ContextualLogger.cs | 7 ++++--- .../Runtime/Logging/LogContext.cs | 5 ++--- .../Logging/LogContextNetworkManager.cs | 3 ++- .../Runtime/Logging/NetworkLog.cs | 18 +++++++++--------- .../Messaging/Messages/ServerLogMessage.cs | 6 +++--- .../Editor/NetworkManagerConfigurationTests.cs | 1 - 7 files changed, 20 insertions(+), 21 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 9969fcde75..62eeab82df 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -11,7 +11,6 @@ using PackageInfo = UnityEditor.PackageManager.PackageInfo; #endif using UnityEngine.SceneManagement; -using Debug = UnityEngine.Debug; namespace Unity.Netcode { diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs b/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs index 6999394066..495b8a9c5f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs @@ -90,7 +90,7 @@ internal void RemoveInfo(string key) [MethodImpl(MethodImplOptions.AggressiveInlining)] public void CaptureFunctionCall([CallerMemberName] string memberName = "") { - Log(LogType.Log, new Context(LogLevel.Developer, memberName, true)); + Log(LogType.Log, new Context(LogLevel.Developer, memberName, true)); } [HideInCallstack] @@ -155,7 +155,8 @@ private string BuildLog(Context context) m_Builder.Append(k_NetcodeHeader); if (m_UseCompatibilityMode) - { ; + { + ; m_Builder.Append(context.Message); } else @@ -174,7 +175,7 @@ private string BuildLog(Context context) /// /// Removes the configured context from the logger when this object is disposed. /// - public readonly struct DisposableContext: IDisposable + public readonly struct DisposableContext : IDisposable { private readonly ContextualLogger m_Logger; private readonly string m_ToClear; diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs index c40ca6696f..d76b6d7d50 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs @@ -1,5 +1,4 @@ using System.Runtime.CompilerServices; -using System.Text; using UnityEngine; using Object = UnityEngine.Object; @@ -69,9 +68,9 @@ public Context AddTag(string msg) return this; } - public Context ForGameObject(GameObject prefabObj) + public Context ForGameObject(GameObject obj) { - RelevantObjectOverride = prefabObj.gameObject; + RelevantObjectOverride = obj; return this; } } diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/LogContextNetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Logging/LogContextNetworkManager.cs index 2a8075e3df..de5a11b888 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/LogContextNetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/LogContextNetworkManager.cs @@ -87,7 +87,8 @@ public readonly void AppendTo(LogBuilder builder) { // [Session Owner] builder.Append(k_SessionOwnerString); - } else if (m_NetworkManager.IsServer) + } + else if (m_NetworkManager.IsServer) { // [Server] builder.Append(k_ServerString); diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs b/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs index 2cd13fccf0..aefa6e6a9a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs @@ -16,7 +16,7 @@ internal struct LogConfiguration /// public static class NetworkLog { - private static readonly ContextualLogger k_Log = new(true); + private static readonly ContextualLogger k_Log = new(true); internal static void SetNetworkManager(NetworkManager networkManager) { @@ -38,7 +38,7 @@ internal static void SetNetworkManager(NetworkManager networkManager) /// /// The message to log [HideInCallstack] - public static void LogInfo(string message, [CallerMemberName] string memberName = "") => k_Log.Info(new Context(LogLevel.Normal, message, memberName)); + public static void LogInfo(string message) => k_Log.Info(new Context(LogLevel.Normal, message)); [HideInCallstack] internal static void LogInfo(Context context) => k_Log.Info(context); @@ -47,7 +47,7 @@ internal static void SetNetworkManager(NetworkManager networkManager) /// /// The message to log [HideInCallstack] - public static void LogWarning(string message, [CallerMemberName] string memberName = "") => k_Log.Warning(new Context(LogLevel.Error, message, memberName)); + public static void LogWarning(string message) => k_Log.Warning(new Context(LogLevel.Error, message)); [HideInCallstack] internal static void LogWarning(Context context) => k_Log.Warning(context); @@ -56,7 +56,7 @@ internal static void SetNetworkManager(NetworkManager networkManager) /// /// The message to log [HideInCallstack] - public static void LogError(string message, [CallerMemberName] string memberName = "") => k_Log.Error(new Context(LogLevel.Error, message, memberName)); + public static void LogError(string message) => k_Log.Error(new Context(LogLevel.Error, message)); [HideInCallstack] internal static void LogError(Context context) => k_Log.Error(context); @@ -67,28 +67,28 @@ internal static void SetNetworkManager(NetworkManager networkManager) /// /// The message to log [HideInCallstack] - public static void LogInfoServer(string message, [CallerMemberName] string memberName = "") => k_Log.InfoServer(new Context(LogLevel.Normal, message, memberName)); + public static void LogInfoServer(string message) => k_Log.InfoServer(new Context(LogLevel.Normal, message)); /// /// Logs an info log locally and on the session owner if possible. /// /// The message to log [HideInCallstack] - public static void LogInfoSessionOwner(string message, [CallerMemberName] string memberName = "") => k_Log.InfoServer(new Context(LogLevel.Normal, message, memberName)); + public static void LogInfoSessionOwner(string message) => k_Log.InfoServer(new Context(LogLevel.Normal, message)); /// /// Logs a warning log locally and on the server if possible. /// /// The message to log [HideInCallstack] - public static void LogWarningServer(string message, [CallerMemberName] string memberName = "") => k_Log.WarningServer(new Context(LogLevel.Error, message, memberName)); + public static void LogWarningServer(string message) => k_Log.WarningServer(new Context(LogLevel.Error, message)); /// /// Logs an error log locally and on the server if possible. /// /// The message to log [HideInCallstack] - public static void LogErrorServer(string message, [CallerMemberName] string memberName = "") => k_Log.ErrorServer(new Context(LogLevel.Error, message, memberName)); + public static void LogErrorServer(string message) => k_Log.ErrorServer(new Context(LogLevel.Error, message)); internal static LogType GetMessageLogType(UnityEngine.LogType engineLogType) { @@ -114,7 +114,7 @@ internal static void SendLogToAuthority(NetworkManager networkManager, LogType l } private const string k_SenderId = "SenderId"; - internal static Context ContextWithSenderId([NotNull] NetworkManager networkManager, LogLevel level, ulong senderId, string message) + internal static Context BuildContextForServerMessage([NotNull] NetworkManager networkManager, LogLevel level, ulong senderId, string message) { var ctx = new Context(level, message, true).AddInfo(k_SenderId, senderId); if (TryGetNetworkObjectName(networkManager, message, out var name)) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs index cc6be184ea..05b3bae37a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs @@ -57,13 +57,13 @@ public void Handle(ref NetworkContext context) switch (LogType) { case NetworkLog.LogType.Info: - networkManager.Log.Info(NetworkLog.ContextWithSenderId(networkManager, LogLevel.Normal, senderId, Message)); + networkManager.Log.Info(NetworkLog.BuildContextForServerMessage(networkManager, LogLevel.Normal, senderId, Message)); break; case NetworkLog.LogType.Warning: - networkManager.Log.Warning(NetworkLog.ContextWithSenderId(networkManager, LogLevel.Error, senderId, Message)); + networkManager.Log.Warning(NetworkLog.BuildContextForServerMessage(networkManager, LogLevel.Error, senderId, Message)); break; case NetworkLog.LogType.Error: - networkManager.Log.Error(NetworkLog.ContextWithSenderId(networkManager, LogLevel.Error, senderId, Message)); + networkManager.Log.Error(NetworkLog.BuildContextForServerMessage(networkManager, LogLevel.Error, senderId, Message)); break; } } diff --git a/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs index 41d300ef50..386c821fea 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs @@ -3,7 +3,6 @@ using NUnit.Framework; using Unity.Netcode.Editor; using Unity.Netcode.Logging; -using Unity.Netcode.RuntimeTests; using Unity.Netcode.Transports.UTP; using UnityEditor.SceneManagement; using UnityEngine; From 4bf7f57da47960b27b0240a3458d0ebcd1d75deb Mon Sep 17 00:00:00 2001 From: Emma Date: Thu, 23 Apr 2026 15:21:05 -0400 Subject: [PATCH 05/16] split branches properly --- com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 62eeab82df..e3fac43b54 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1057,7 +1057,7 @@ private void Awake() Log = new ContextualLogger(this, this); } - NetworkConfig?.InitializePrefabs(Log); + NetworkConfig?.InitializePrefabs(); UnityEngine.SceneManagement.SceneManager.sceneUnloaded += OnSceneUnloaded; #if UNITY_EDITOR @@ -1263,7 +1263,7 @@ internal void Initialize(bool server) BehaviourUpdater = new NetworkBehaviourUpdater(); BehaviourUpdater.Initialize(this); - NetworkConfig.InitializePrefabs(Log); + NetworkConfig.InitializePrefabs(); PrefabHandler.RegisterPlayerPrefab(); #if UNITY_EDITOR BeginNetworkSession(); From dcec54fd510d8c5efe910a4b518b657d8193bfda Mon Sep 17 00:00:00 2001 From: Emma Date: Thu, 23 Apr 2026 16:51:39 -0400 Subject: [PATCH 06/16] Split branches pt 2 electric boogaloo --- .../Tests/Editor/NetworkManagerConfigurationTests.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs index 386c821fea..d6bbcef994 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs @@ -150,8 +150,7 @@ public void WhenNetworkConfigContainsOldPrefabList_TheyMigrateProperlyToTheNewLi new NetworkPrefab { Prefab = overriddenPrefab.gameObject, Override = NetworkPrefabOverride.Prefab, OverridingTargetPrefab = overridingTargetPrefab.gameObject, SourcePrefabToOverride = sourcePrefabToOverride.gameObject, SourceHashToOverride = 123456 } }; - var log = new ContextualLogger(); - networkConfig.InitializePrefabs(log); + networkConfig.InitializePrefabs(); Assert.IsNull(networkConfig.OldPrefabList); Assert.IsNotNull(networkConfig.Prefabs); @@ -309,8 +308,7 @@ public void WhenThereAreUninitializedElementsInPrefabsList_NoErrors() Assert.That(networkConfig.Prefabs.NetworkPrefabsLists.Count, Is.EqualTo(2), "Failed test setup: Both the null element and the list containing a null Prefab should be listed"); - var log = new ContextualLogger(); - networkConfig.InitializePrefabs(log); + networkConfig.InitializePrefabs(); Assert.That(networkConfig.Prefabs.NetworkPrefabsLists.Count, Is.EqualTo(1), "null element should have been removed"); Assert.That(networkConfig.Prefabs.Prefabs.Count, Is.EqualTo(0), "Invalid prefab was registered"); From 522e0e92c935536b671a356cdb46efc5405ddc31 Mon Sep 17 00:00:00 2001 From: Emma Date: Thu, 23 Apr 2026 18:31:22 -0400 Subject: [PATCH 07/16] dotnet-fix --- .../Tests/Editor/NetworkManagerConfigurationTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs index d6bbcef994..9818552644 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs @@ -2,7 +2,6 @@ using System.Text.RegularExpressions; using NUnit.Framework; using Unity.Netcode.Editor; -using Unity.Netcode.Logging; using Unity.Netcode.Transports.UTP; using UnityEditor.SceneManagement; using UnityEngine; From ae2267870a2f8db38488df45d6da86999565fd2e Mon Sep 17 00:00:00 2001 From: Emma Date: Thu, 23 Apr 2026 20:45:48 -0400 Subject: [PATCH 08/16] Fix log lines in test --- .../Runtime/Logging/ContextualLogger.cs | 2 +- .../Runtime/DistributedAuthority/OwnershipPermissionsTests.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs b/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs index 495b8a9c5f..f5f07ed161 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs @@ -136,7 +136,7 @@ private void Log(LogType logType, Context context) 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) + if (m_ManagerContext.LogLevel > context.Level) { return; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs index cbd0e67a48..2be21223b3 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Text; +using System.Text.RegularExpressions; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; @@ -445,7 +446,7 @@ public IEnumerator ChangeOwnershipWithoutObservers() authorityInstance.ChangeOwnership(otherClient.LocalClientId); var senderId = authority.LocalClientId; var receiverId = otherClient.LocalClientId; - LogAssert.Expect(LogType.Warning, $"[Netcode-Session-Owner Sender={senderId}] [Invalid Owner] Cannot send Ownership change as client-{receiverId} cannot see {authorityInstance.name}! Use NetworkShow first."); + LogAssert.Expect(LogType.Warning, new Regex("Cannot send Ownership change as client cannot see NetworkObject")); Assert.True(authorityInstance.IsOwner, $"[Ownership Check] Client-{senderId} should still own this object!"); // Now re-add the client to the Observers list and try to change ownership From 707786097f1931851c97975a9697e73f6bfd574f Mon Sep 17 00:00:00 2001 From: Emma Date: Thu, 23 Apr 2026 20:46:30 -0400 Subject: [PATCH 09/16] Fix up SpawnManager tests --- .../Connection/NetworkConnectionManager.cs | 9 +- .../Runtime/Spawning/NetworkSpawnManager.cs | 59 +++-- .../Tests/Runtime/NetworkSpawnManagerTests.cs | 218 ++++++++---------- 3 files changed, 136 insertions(+), 150 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index f146101441..bcdb8ba05a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -1343,7 +1343,14 @@ internal void OnClientDisconnectFromServer(ulong clientId) } else if (!NetworkManager.ShutdownInProgress) { - playerObject.RemoveOwnership(); + if (NetworkManager.DistributedAuthorityMode) + { + NetworkManager.SpawnManager.ChangeOwnership(playerObject, NetworkManager.LocalClientId, true); + } + else + { + playerObject.RemoveOwnership(); + } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 543aa15912..ecb6180bbd 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Text; +using Unity.Netcode.Logging; using UnityEngine; using Object = UnityEngine.Object; @@ -157,18 +158,34 @@ private void RemovePlayerObject(NetworkObject playerObject, bool destroyingObjec } playerObject.IsPlayerObject = false; m_PlayerObjects.Remove(playerObject); - if (m_PlayerObjectsTable.ContainsKey(playerObject.OwnerClientId)) + + var originalOwner = playerObject.OwnerClientId; + + // Try on the current owner's table + if (m_PlayerObjectsTable.TryGetValue(playerObject.OwnerClientId, out var ownerTable) && ownerTable.Remove(playerObject)) { - m_PlayerObjectsTable[playerObject.OwnerClientId].Remove(playerObject); - if (m_PlayerObjectsTable[playerObject.OwnerClientId].Count == 0) + if (ownerTable.Count == 0) { m_PlayerObjectsTable.Remove(playerObject.OwnerClientId); } } - // If the client exists locally and we are destroying... - if (NetworkManager.ConnectionManager.ConnectedClients.ContainsKey(playerObject.OwnerClientId) && destroyingObject) + // If the object wasn't removed from the owner's list, we need to check on all lists + // The ownership could have changed since it was created + else + { + foreach (var (owner, playerObjects) in m_PlayerObjectsTable) + { + if (playerObjects.Remove(playerObject)) + { + originalOwner = owner; + break; + } + } + } + + // If the client exists locally, and we are destroying... + if (destroyingObject && NetworkManager.ConnectionManager.ConnectedClients.TryGetValue(originalOwner, out var client)) { - var client = NetworkManager.ConnectionManager.ConnectedClients[playerObject.OwnerClientId]; // and the client's currently assigned player object is what is being destroyed... if (client != null && client.PlayerObject == playerObject) { @@ -378,33 +395,25 @@ public List GetPlayerNetworkObjects(ulong clientId) } /// - /// Returns the player object with a given clientId or null if one does not exist. This is only valid server side. + /// Returns the player object with a given clientId or null if one does not exist. /// + /// + /// In client-server only the server can get other player's player objects. + /// /// the client identifier of the player /// The player object with a given clientId or null if one does not exist public NetworkObject GetPlayerNetworkObject(ulong clientId) { - if (!NetworkManager.DistributedAuthorityMode) + // Only the server can get other player's player objects in client-server mode + if (!NetworkManager.DistributedAuthorityMode && !NetworkManager.IsServer && NetworkManager.LocalClientId != clientId) { - if (!NetworkManager.IsServer && NetworkManager.LocalClientId != clientId) - { - if (NetworkManager.LogLevel <= LogLevel.Error) - { - NetworkLog.LogErrorServer($"{clientId} Only the server can find player objects from other clients."); - } - return null; - } - if (TryGetNetworkClient(clientId, out NetworkClient networkClient)) - { - return networkClient.PlayerObject; - } + NetworkManager.Log.Error(new Context(LogLevel.Error, "Only the server can find player objects from other clients.")); + return null; } - else + + if (m_PlayerObjectsTable.TryGetValue(clientId, out var playerObjects)) { - if (m_PlayerObjectsTable.ContainsKey(clientId)) - { - return m_PlayerObjectsTable[clientId].First(); - } + return playerObjects.Count > 0 ? playerObjects[0] : null; } return null; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkSpawnManagerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkSpawnManagerTests.cs index a0d51696bb..157836faaf 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkSpawnManagerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkSpawnManagerTests.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Text.RegularExpressions; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine.TestTools; @@ -9,164 +10,133 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(HostOrServer.Host)] internal class NetworkSpawnManagerTests : NetcodeIntegrationTest { - private ulong serverSideClientId => NetworkManager.ServerClientId; - private ulong clientSideClientId => m_ClientNetworkManagers[0].LocalClientId; - private ulong otherClientSideClientId => m_ClientNetworkManagers[1].LocalClientId; - protected override int NumberOfClients => 2; - // TODO: [CmbServiceTests] Adapt to run with the service. Combine server and client tests - protected override bool UseCMBService() - { - return false; - } - public NetworkSpawnManagerTests(HostOrServer hostOrServer) : base(hostOrServer) { } [Test] - public void TestServerCanAccessItsOwnPlayer() - { - // server can access its own player - var serverSideServerPlayerObject = m_ServerNetworkManager.SpawnManager.GetPlayerNetworkObject(serverSideClientId); - Assert.NotNull(serverSideServerPlayerObject); - Assert.AreEqual(serverSideClientId, serverSideServerPlayerObject.OwnerClientId); - } - - - /// - /// Test was converted from a Test to UnityTest so distributed authority mode will pass this test. - /// In distributed authority mode, client-side player spawning is enabled by default which requires - /// all client (including DAHost) instances to wait for all players to be spawned. - /// - [UnityTest] - public IEnumerator TestServerCanAccessOtherPlayers() - { - yield return null; - // server can access other players - var serverSideClientPlayerObject = m_ServerNetworkManager.SpawnManager.GetPlayerNetworkObject(clientSideClientId); - Assert.NotNull(serverSideClientPlayerObject); - Assert.AreEqual(clientSideClientId, serverSideClientPlayerObject.OwnerClientId); - - var serverSideOtherClientPlayerObject = m_ServerNetworkManager.SpawnManager.GetPlayerNetworkObject(otherClientSideClientId); - Assert.NotNull(serverSideOtherClientPlayerObject); - Assert.AreEqual(otherClientSideClientId, serverSideOtherClientPlayerObject.OwnerClientId); - } - - [Test] - public void TestClientCantAccessServerPlayer() + public void TestGetPlayerNetworkObject() { - if (m_DistributedAuthority) + foreach (var toTest in m_NetworkManagers) { - VerboseDebug($"Ignoring test: Clients have access to other player objects in {m_NetworkTopologyType} mode."); - return; + foreach (var toGet in m_NetworkManagers) + { + // GetPlayerNetworkObject should only be able to get the object + // - When using DA + // - When testing the Server NetworkManager + // - And when the client is getting their own PlayerObject. + var canFetch = m_DistributedAuthority || toTest.IsServer || toTest == toGet; + + if (!canFetch) + { + LogAssert.Expect(UnityEngine.LogType.Error, new Regex("Only the server can find player objects from other clients.")); + } + + var playerObject = toTest.SpawnManager.GetPlayerNetworkObject(toGet.LocalClientId); + + if (canFetch) + { + Assert.That(playerObject, Is.Not.Null); + Assert.That(toGet.LocalClientId, Is.EqualTo(playerObject.OwnerClientId)); + } + else + { + Assert.That(playerObject, Is.Null); + } + } } - // client can't access server player - string expectedLog = $"[Netcode-Server Sender=0] {serverSideClientId} Only the server can find player objects from other clients."; - LogAssert.Expect(UnityEngine.LogType.Error, expectedLog); - m_ClientNetworkManagers[0].SpawnManager.GetPlayerNetworkObject(serverSideClientId); - } - [Test] - public void TestClientCanAccessOwnPlayer() - { - // client can access own player - var clientSideClientPlayerObject = m_ClientNetworkManagers[0].SpawnManager.GetPlayerNetworkObject(clientSideClientId); - Assert.NotNull(clientSideClientPlayerObject); - Assert.AreEqual(clientSideClientId, clientSideClientPlayerObject.OwnerClientId); + // finally, test that an invalid clientId returns a null object + var invalid = GetAuthorityNetworkManager().SpawnManager.GetPlayerNetworkObject(9999); + Assert.That(invalid, Is.Null); } [Test] - public void TestClientCanAccessOtherPlayer() + public void TestGetLocalPlayerObject() { - - if (!m_DistributedAuthority) + foreach (var manager in m_NetworkManagers) { - VerboseDebug($"Ignoring test: Clients do not have access to other player objects in {m_NetworkTopologyType} mode."); - return; + var playerObject = manager.SpawnManager.GetLocalPlayerObject(); + Assert.That(playerObject, Is.Not.Null); + Assert.That(manager.LocalClientId, Is.EqualTo(playerObject.OwnerClientId)); + Assert.That(manager.LocalClient.PlayerObject, Is.EqualTo(playerObject)); } - - var otherClientPlayer = m_ClientNetworkManagers[0].SpawnManager.GetPlayerNetworkObject(otherClientSideClientId); - Assert.NotNull(otherClientPlayer, $"Failed to obtain Client{otherClientSideClientId}'s player object!"); } - [Test] - public void TestClientCantAccessOtherPlayer() + public enum DestroyWithOwner { - if (m_DistributedAuthority) - { - VerboseDebug($"Ignoring test: Clients have access to other player objects in {m_NetworkTopologyType} mode."); - return; - } - - // client can't access other player - string expectedLog = $"[Netcode-Server Sender=0] {otherClientSideClientId} Only the server can find player objects from other clients."; - LogAssert.Expect(UnityEngine.LogType.Error, expectedLog); - m_ClientNetworkManagers[0].SpawnManager.GetPlayerNetworkObject(otherClientSideClientId); + DestroyWithOwner, + DontDestroyWithOwner } - [Test] - public void TestServerGetsNullValueIfInvalidId() - { - // server gets null value if invalid id - var nullPlayer = m_ServerNetworkManager.SpawnManager.GetPlayerNetworkObject(9999); - Assert.Null(nullPlayer); - } - - [Test] - public void TestServerCanUseGetLocalPlayerObject() - { - // test server can use GetLocalPlayerObject - var serverSideServerPlayerObject = m_ServerNetworkManager.SpawnManager.GetLocalPlayerObject(); - Assert.NotNull(serverSideServerPlayerObject); - Assert.AreEqual(serverSideClientId, serverSideServerPlayerObject.OwnerClientId); - } - - [Test] - public void TestClientCanUseGetLocalPlayerObject() + [UnityTest] + public IEnumerator TestPlayerPrefabConnectAndDisconnect([Values] DestroyWithOwner destroySetting) { - // test client can use GetLocalPlayerObject - var clientSideClientPlayerObject = m_ClientNetworkManagers[0].SpawnManager.GetLocalPlayerObject(); - Assert.NotNull(clientSideClientPlayerObject); - Assert.AreEqual(clientSideClientId, clientSideClientPlayerObject.OwnerClientId); - } + var destroyWithOwner = destroySetting == DestroyWithOwner.DestroyWithOwner; + var authority = GetAuthorityNetworkManager(); + // Regression test: Ensure the authority's player object is set + Assert.That(authority.LocalClient.PlayerObject != null, Is.True, "The server should have a player object!"); - private bool m_ClientDisconnected; + // Mark PlayerPrefab as DontDestroyWithOwner + m_PlayerPrefab.GetComponent().DontDestroyWithOwner = !destroyWithOwner; - [UnityTest] - public IEnumerator TestConnectAndDisconnect() - { // test when client connects, player object is now available - yield return CreateAndStartNewClient(); - var newClientNetworkManager = m_ClientNetworkManagers[NumberOfClients]; - var newClientLocalClientId = newClientNetworkManager.LocalClientId; + var newClient = CreateNewClient(); + yield return StartClient(newClient); + var newClientId = newClient.LocalClientId; // test new client can get that itself locally - var newPlayerObject = newClientNetworkManager.SpawnManager.GetLocalPlayerObject(); + var newPlayerObject = newClient.SpawnManager.GetLocalPlayerObject(); Assert.NotNull(newPlayerObject); - Assert.AreEqual(newClientLocalClientId, newPlayerObject.OwnerClientId); + Assert.AreEqual(newClientId, newPlayerObject.OwnerClientId); + // test server can get that new client locally - var serverSideNewClientPlayer = m_ServerNetworkManager.SpawnManager.GetPlayerNetworkObject(newClientLocalClientId); + var serverSideNewClientPlayer = authority.SpawnManager.GetPlayerNetworkObject(newClientId); Assert.NotNull(serverSideNewClientPlayer); - Assert.AreEqual(newClientLocalClientId, serverSideNewClientPlayer.OwnerClientId); + Assert.AreEqual(newClientId, serverSideNewClientPlayer.OwnerClientId); // test when client disconnects, player object no longer available. - var nbConnectedClients = m_ServerNetworkManager.ConnectedClients.Count; - m_ClientDisconnected = false; - newClientNetworkManager.OnClientDisconnectCallback += ClientNetworkManager_OnClientDisconnectCallback; - m_ServerNetworkManager.DisconnectClient(newClientLocalClientId); - yield return WaitForConditionOrTimeOut(() => m_ClientDisconnected); - Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for client to disconnect"); + var nbConnectedClients = authority.ConnectedClients.Count; + authority.DisconnectClient(newClientId); + + yield return WaitForConditionOrTimeOut(() => !newClient.IsConnectedClient); + AssertOnTimeout("Timed out waiting for client to disconnect"); + // Call this to clean up NetcodeIntegrationTestHelpers - NetcodeIntegrationTestHelpers.StopOneClient(newClientNetworkManager); + yield return StopOneClient(newClient); - Assert.AreEqual(m_ServerNetworkManager.ConnectedClients.Count, nbConnectedClients - 1); - serverSideNewClientPlayer = m_ServerNetworkManager.SpawnManager.GetPlayerNetworkObject(newClientLocalClientId); - Assert.Null(serverSideNewClientPlayer); - } + Assert.AreEqual(authority.ConnectedClients.Count, nbConnectedClients - 1); + Assert.True(newPlayerObject == null, "The client's player object should have been destroyed!"); - private void ClientNetworkManager_OnClientDisconnectCallback(ulong obj) - { - m_ClientDisconnected = true; + if (destroyWithOwner) + { + Assert.That(serverSideNewClientPlayer == null, Is.True, "The server's version of the client's player object should have been destroyed"); + } + else + { + Assert.That(serverSideNewClientPlayer == null, Is.False, "The server's version of the client's player object shouldn't have been destroyed!"); + Assert.That(serverSideNewClientPlayer.OwnerClientId, Is.EqualTo(authority.LocalClientId), "Ownership should have transferred to the authority!"); + Assert.That(serverSideNewClientPlayer.IsPlayerObject, Is.True, $"{nameof(NetworkObject.IsPlayerObject)} should still be set!"); + + // Requesting the player's object after they've left should still while the object hasn't been destroyed + var playerObject = authority.SpawnManager.GetPlayerNetworkObject(newClientId); + Assert.That(playerObject != null, Is.True, "The authority should still be able to get the player object after the client has disconnected"); + + // Despawn and destroy the player object + playerObject.Despawn(); + + // Check that now the player object is null + yield return WaitForConditionOrTimeOut(() => playerObject == null); + AssertOnTimeout("Timed out waiting for the object to be destroyed."); + + // Regression test: + // check that the authority's player object isn't destroyed + Assert.That(authority.LocalClient.PlayerObject != null, Is.True, "The server's player object should not have been destroyed!"); + } + + // sanity check that requesting the object from the client who left after the object was destroyed is now null + var sanity = authority.SpawnManager.GetPlayerNetworkObject(newClientId); + Assert.Null(sanity, $"{nameof(NetworkSpawnManager.GetPlayerNetworkObject)} shouldn't be able to get the player object after the client has disconnected!"); } } } From e2ad473bad97f35950c0d206b14e7569979dd568 Mon Sep 17 00:00:00 2001 From: Emma Date: Thu, 23 Apr 2026 22:34:17 -0400 Subject: [PATCH 10/16] Small test fixes --- .../Editor/NetworkManagerHelper.cs | 2 +- .../Runtime/Core/NetworkManager.cs | 44 ++++++++++++++++--- .../Runtime/Logging/ContextualLogger.cs | 1 - .../Runtime/Logging/LogContext.cs | 2 +- .../Logging/LogContextNetworkManager.cs | 12 ++++- .../Runtime/Logging/NetworkLog.cs | 28 ++++-------- .../Runtime/Spawning/NetworkSpawnManager.cs | 5 +-- .../NetworkManagerConfigurationTests.cs | 9 ++-- .../OwnershipPermissionsTests.cs | 1 - .../Runtime/NestedNetworkManagerTests.cs | 10 +---- .../NetworkObjectDestroyTests.cs | 2 +- 11 files changed, 66 insertions(+), 50 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs index 5c7e554baf..aa1735c651 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs @@ -187,7 +187,7 @@ public bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool var transform = networkManager.transform; var isParented = transform.root != transform; - var message = NetworkManager.GenerateNestedNetworkManagerMessage(transform); + var message = $"{transform.name} is nested under {transform.root.name}. NetworkManager cannot be nested.\n"; if (s_LastKnownNetworkManagerParents.ContainsKey(networkManager) && !ignoreNetworkManagerCache) { // If we have already notified the user, then don't notify them again diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index e3fac43b54..87c9e3093b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1019,11 +1019,6 @@ internal bool NetworkManagerCheckForParent(bool ignoreNetworkManagerCache = fals return isParented; } - internal static string GenerateNestedNetworkManagerMessage(Transform transform) - { - return $"{transform.name} is nested under {transform.root.name}. NetworkManager cannot be nested.\n"; - } - /// /// Handle runtime detection for parenting the NetworkManager's GameObject under another GameObject /// @@ -1835,6 +1830,45 @@ internal void OnValidate() Log.Warning(new Context(LogLevel.Normal, $"{nameof(NetworkManager)} cannot be a {nameof(NetworkObject)}.")); } + var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene(); + + // If the scene is not dirty or the asset database is currently updating then we can skip updating the NetworkPrefab information + if (!activeScene.isDirty || EditorApplication.isUpdating) + { + return; + } + + // During OnValidate we will always clear out NetworkPrefabOverrideLinks and rebuild it + NetworkConfig.Prefabs.NetworkPrefabOverrideLinks.Clear(); + + var prefabs = NetworkConfig.Prefabs.Prefabs; + // Check network prefabs and assign to dictionary for quick look up + for (int i = 0; i < prefabs.Count; i++) + { + var networkPrefab = prefabs[i]; + var networkPrefabGo = networkPrefab?.Prefab; + if (networkPrefabGo == null) + { + continue; + } + + var networkObject = networkPrefabGo.GetComponent(); + if (networkObject == null) + { + Log.Warning(new Context(LogLevel.Normal, $"Cannot register prefab to {nameof(NetworkManager)}, missing a {nameof(NetworkObject)} component at its root").AddObject(networkPrefab.Prefab)); + continue; + } + + { + var childNetworkObjects = new List(); + networkPrefabGo.GetComponentsInChildren(true, childNetworkObjects); + if (childNetworkObjects.Count > 1) // total count = 1 root NetworkObject + n child NetworkObjects + { + 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)").AddObject(networkPrefab.Prefab)); + } + } + } + try { OnValidateComponent(); diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs b/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs index f5f07ed161..0ea2f5d858 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs @@ -156,7 +156,6 @@ private string BuildLog(Context context) if (m_UseCompatibilityMode) { - ; m_Builder.Append(context.Message); } else diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs index d76b6d7d50..a6f4a56af6 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs @@ -68,7 +68,7 @@ public Context AddTag(string msg) return this; } - public Context ForGameObject(GameObject obj) + public Context AddObject(Object obj) { RelevantObjectOverride = obj; return this; diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/LogContextNetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Logging/LogContextNetworkManager.cs index de5a11b888..a89e7455ce 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/LogContextNetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/LogContextNetworkManager.cs @@ -35,10 +35,18 @@ public readonly void TrySendMessage(LogType logType, string message) if (m_NetworkManager != null && m_NetworkManager.IsListening && (m_NetworkManager?.NetworkConfig.EnableNetworkLogs ?? false) - && (m_NetworkManager.IsServer || m_NetworkManager.LocalClient.IsSessionOwner)) + && !m_NetworkManager.IsServer && !m_NetworkManager.LocalClient.IsSessionOwner) { var messageType = NetworkLog.GetMessageLogType(logType); - NetworkLog.SendLogToAuthority(m_NetworkManager, messageType, m_NetworkManager.LocalClientId, message); + + var networkMessage = new ServerLogMessage + { + LogType = messageType, + Message = message, + SenderId = m_NetworkManager.LocalClientId + }; + var size = m_NetworkManager.ConnectionManager.SendMessage(ref networkMessage, MessageDeliveryType.DefaultDelivery, NetworkManager.ServerClientId); + m_NetworkManager.NetworkMetrics.TrackServerLogSent(NetworkManager.ServerClientId, (uint)logType, size); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs b/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs index aefa6e6a9a..11266b58fd 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs @@ -16,6 +16,7 @@ internal struct LogConfiguration /// public static class NetworkLog { + internal static LogConfiguration Config; private static readonly ContextualLogger k_Log = new(true); internal static void SetNetworkManager(NetworkManager networkManager) @@ -29,8 +30,6 @@ internal static void SetNetworkManager(NetworkManager networkManager) // [Obsolete("Use the LogLevel directly on the NetworkManager instead")] public static LogLevel CurrentLogLevel => NetworkManager.Singleton == null ? LogLevel.Normal : NetworkManager.Singleton.LogLevel; - internal static LogConfiguration Config = new LogConfiguration(); - // internal logging /// @@ -38,7 +37,7 @@ internal static void SetNetworkManager(NetworkManager networkManager) /// /// The message to log [HideInCallstack] - public static void LogInfo(string message) => k_Log.Info(new Context(LogLevel.Normal, message)); + public static void LogInfo(string message) => k_Log.Info(new Context(LogLevel.Normal, message, true)); [HideInCallstack] internal static void LogInfo(Context context) => k_Log.Info(context); @@ -47,7 +46,7 @@ internal static void SetNetworkManager(NetworkManager networkManager) /// /// The message to log [HideInCallstack] - public static void LogWarning(string message) => k_Log.Warning(new Context(LogLevel.Error, message)); + public static void LogWarning(string message) => k_Log.Warning(new Context(LogLevel.Error, message, true)); [HideInCallstack] internal static void LogWarning(Context context) => k_Log.Warning(context); @@ -56,7 +55,7 @@ internal static void SetNetworkManager(NetworkManager networkManager) /// /// The message to log [HideInCallstack] - public static void LogError(string message) => k_Log.Error(new Context(LogLevel.Error, message)); + public static void LogError(string message) => k_Log.Error(new Context(LogLevel.Error, message, true)); [HideInCallstack] internal static void LogError(Context context) => k_Log.Error(context); @@ -67,28 +66,28 @@ internal static void SetNetworkManager(NetworkManager networkManager) /// /// The message to log [HideInCallstack] - public static void LogInfoServer(string message) => k_Log.InfoServer(new Context(LogLevel.Normal, message)); + public static void LogInfoServer(string message) => k_Log.InfoServer(new Context(LogLevel.Normal, message, true)); /// /// Logs an info log locally and on the session owner if possible. /// /// The message to log [HideInCallstack] - public static void LogInfoSessionOwner(string message) => k_Log.InfoServer(new Context(LogLevel.Normal, message)); + public static void LogInfoSessionOwner(string message) => k_Log.InfoServer(new Context(LogLevel.Normal, message, true)); /// /// Logs a warning log locally and on the server if possible. /// /// The message to log [HideInCallstack] - public static void LogWarningServer(string message) => k_Log.WarningServer(new Context(LogLevel.Error, message)); + public static void LogWarningServer(string message) => k_Log.WarningServer(new Context(LogLevel.Error, message, true)); /// /// Logs an error log locally and on the server if possible. /// /// The message to log [HideInCallstack] - public static void LogErrorServer(string message) => k_Log.ErrorServer(new Context(LogLevel.Error, message)); + public static void LogErrorServer(string message) => k_Log.ErrorServer(new Context(LogLevel.Error, message, true)); internal static LogType GetMessageLogType(UnityEngine.LogType engineLogType) { @@ -101,17 +100,6 @@ internal static LogType GetMessageLogType(UnityEngine.LogType engineLogType) }; } - internal static void SendLogToAuthority(NetworkManager networkManager, LogType logType, ulong senderId, string message) - { - var networkMessage = new ServerLogMessage - { - LogType = logType, - Message = message, - SenderId = senderId - }; - var size = networkManager.ConnectionManager.SendMessage(ref networkMessage, MessageDeliveryType.DefaultDelivery, NetworkManager.ServerClientId); - networkManager.NetworkMetrics.TrackServerLogSent(NetworkManager.ServerClientId, (uint)logType, size); - } private const string k_SenderId = "SenderId"; internal static Context BuildContextForServerMessage([NotNull] NetworkManager networkManager, LogLevel level, ulong senderId, string message) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index ecb6180bbd..ecf2036aa2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -572,10 +572,7 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool if (!networkObject.Observers.Contains(clientId)) { - if (NetworkManager.LogLevel <= LogLevel.Developer) - { - NetworkLog.LogWarningServer($"[Invalid Owner] Cannot send Ownership change as client-{clientId} cannot see {networkObject.name}! Use {nameof(NetworkObject.NetworkShow)} first."); - } + NetworkManager.Log.WarningServer(new Context(LogLevel.Developer, $"Cannot send Ownership change as client cannot see {nameof(NetworkObject)}! Use {nameof(NetworkObject.NetworkShow)} first.").AddInfo("Invalid Client", clientId).AddTag(networkObject.name).AddObject(networkObject)); return; } diff --git a/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs index 9818552644..afce5b3d40 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs @@ -2,6 +2,7 @@ using System.Text.RegularExpressions; using NUnit.Framework; using Unity.Netcode.Editor; +using Unity.Netcode.Logging; using Unity.Netcode.Transports.UTP; using UnityEditor.SceneManagement; using UnityEngine; @@ -39,11 +40,8 @@ public void NestedNetworkManagerCheck() // Make our NetworkManager's GameObject nested networkManagerObject.transform.parent = parent.transform; - // Pre-generate the error message we are expecting to see - var messageToCheck = NetworkManager.GenerateNestedNetworkManagerMessage(networkManagerObject.transform); - // Trap for the nested NetworkManager exception - LogAssert.Expect(LogType.Error, new Regex(messageToCheck)); + LogAssert.Expect(LogType.Error, new Regex("NetworkManager cannot be nested")); // Since this is an in-editor test, we must force this invocation NetworkManagerHelper.Singleton.NotifyUserOfNestedNetworkManager(networkManager, false, true); @@ -120,12 +118,11 @@ public void NestedNetworkObjectPrefabCheck() networkManager.OnValidate(); // Expect a warning - LogAssert.Expect(LogType.Warning, new Regex($@"{parent.name}\] Prefab has child {nameof(NetworkObject)}\(s\) but they will not be spawned across the network \(unsupported {nameof(NetworkPrefab)} setup\)")); + LogAssert.Expect(LogType.Warning, new Regex(@"Prefab has child NetworkObject\(s\) but they will not be spawned across the network")); // Clean up Object.DestroyImmediate(networkManagerObject); Object.DestroyImmediate(parent); - } [Test] diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs index 2be21223b3..adeae853aa 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs @@ -445,7 +445,6 @@ public IEnumerator ChangeOwnershipWithoutObservers() // ChangeOwnership should fail authorityInstance.ChangeOwnership(otherClient.LocalClientId); var senderId = authority.LocalClientId; - var receiverId = otherClient.LocalClientId; LogAssert.Expect(LogType.Warning, new Regex("Cannot send Ownership change as client cannot see NetworkObject")); Assert.True(authorityInstance.IsOwner, $"[Ownership Check] Client-{senderId} should still own this object!"); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NestedNetworkManagerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NestedNetworkManagerTests.cs index 95b23182c9..9f2d76dd7b 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NestedNetworkManagerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NestedNetworkManagerTests.cs @@ -1,3 +1,4 @@ +using System.Text.RegularExpressions; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using Unity.Netcode.Transports.UTP; @@ -29,15 +30,8 @@ public void CheckNestedNetworkManager() // Make our NetworkManager's GameObject nested networkManagerObject.transform.parent = parent.transform; - // Generate the error message we are expecting to see - var messageToCheck = NetworkManager.GenerateNestedNetworkManagerMessage(networkManagerObject.transform); - // Trap for the nested NetworkManager exception -#if UNITY_EDITOR - LogAssert.Expect(LogType.Error, messageToCheck); -#else - LogAssert.Expect(LogType.Exception, $"Exception: {messageToCheck}"); -#endif + LogAssert.Expect(LogType.Error, new Regex("NetworkManager cannot be nested")); // Clean up Object.Destroy(parent); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs index 18c5b4554f..37ce83bb96 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs @@ -193,7 +193,7 @@ private bool HaveLogsBeenReceived() return false; } - if (!NetcodeLogAssert.HasLogBeenReceived(LogType.Error, $"[Netcode-Server Sender={m_ClientNetworkManagers[0].LocalClientId}] [Invalid Destroy][{m_ClientPlayerName}][NetworkObjectId:{m_ClientNetworkObjectId}] Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call Destroy or Despawn on the server/host instead.")) + if (!NetcodeLogAssert.HasLogBeenReceived(LogType.Error, $"[Netcode] [SenderId:{m_ClientNetworkManagers[0].LocalClientId}] [Invalid Destroy][{m_ClientPlayerName}][NetworkObjectId:{m_ClientNetworkObjectId}] Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call Destroy or Despawn on the server/host instead.")) { return false; } From 603cb45edeec2dbe29d047c0948ea991dc6de5fc Mon Sep 17 00:00:00 2001 From: Emma Date: Thu, 23 Apr 2026 22:55:16 -0400 Subject: [PATCH 11/16] dotnet-fix --- com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs index a6f4a56af6..8c6cbd0bf1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs @@ -1,5 +1,4 @@ using System.Runtime.CompilerServices; -using UnityEngine; using Object = UnityEngine.Object; namespace Unity.Netcode.Logging From 155eb267e4b455d28c88045ba4755d26a45f299d Mon Sep 17 00:00:00 2001 From: Emma Date: Thu, 23 Apr 2026 23:07:04 -0400 Subject: [PATCH 12/16] fix --- .../Tests/Editor/NetworkManagerConfigurationTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs index afce5b3d40..ea4a4b155b 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs @@ -2,7 +2,6 @@ using System.Text.RegularExpressions; using NUnit.Framework; using Unity.Netcode.Editor; -using Unity.Netcode.Logging; using Unity.Netcode.Transports.UTP; using UnityEditor.SceneManagement; using UnityEngine; From 7bd99357ad534eb4485437b2cb33ee83a9b67d18 Mon Sep 17 00:00:00 2001 From: Emma Date: Thu, 23 Apr 2026 23:28:11 -0400 Subject: [PATCH 13/16] Fix the build --- com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs | 2 +- com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs index aa1735c651..5c7e554baf 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs @@ -187,7 +187,7 @@ public bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool var transform = networkManager.transform; var isParented = transform.root != transform; - var message = $"{transform.name} is nested under {transform.root.name}. NetworkManager cannot be nested.\n"; + var message = NetworkManager.GenerateNestedNetworkManagerMessage(transform); if (s_LastKnownNetworkManagerParents.ContainsKey(networkManager) && !ignoreNetworkManagerCache) { // If we have already notified the user, then don't notify them again diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 87c9e3093b..a1ec92c33a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1019,6 +1019,11 @@ internal bool NetworkManagerCheckForParent(bool ignoreNetworkManagerCache = fals return isParented; } + internal static string GenerateNestedNetworkManagerMessage(Transform transform) + { + return $"{transform.name} is nested under {transform.root.name}. NetworkManager cannot be nested.\n"; + } + /// /// Handle runtime detection for parenting the NetworkManager's GameObject under another GameObject /// From 7fe3fb90d0119ad4a0f2158fe5e4db4eaf4e2f6c Mon Sep 17 00:00:00 2001 From: Emma Date: Fri, 24 Apr 2026 08:53:28 -0400 Subject: [PATCH 14/16] Fix DA PlayerPrefab tests --- .../Tests/Runtime/NetworkSpawnManagerTests.cs | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkSpawnManagerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkSpawnManagerTests.cs index 157836faaf..710c1b0662 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkSpawnManagerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkSpawnManagerTests.cs @@ -97,10 +97,14 @@ public IEnumerator TestPlayerPrefabConnectAndDisconnect([Values] DestroyWithOwne // test when client disconnects, player object no longer available. var nbConnectedClients = authority.ConnectedClients.Count; - authority.DisconnectClient(newClientId); - yield return WaitForConditionOrTimeOut(() => !newClient.IsConnectedClient); - AssertOnTimeout("Timed out waiting for client to disconnect"); + if (!m_DistributedAuthority) + { + authority.DisconnectClient(newClientId); + + yield return WaitForConditionOrTimeOut(() => !newClient.IsConnectedClient); + AssertOnTimeout("Timed out waiting for client to disconnect"); + } // Call this to clean up NetcodeIntegrationTestHelpers yield return StopOneClient(newClient); @@ -115,11 +119,18 @@ public IEnumerator TestPlayerPrefabConnectAndDisconnect([Values] DestroyWithOwne else { Assert.That(serverSideNewClientPlayer == null, Is.False, "The server's version of the client's player object shouldn't have been destroyed!"); - Assert.That(serverSideNewClientPlayer.OwnerClientId, Is.EqualTo(authority.LocalClientId), "Ownership should have transferred to the authority!"); + var newOwner = authority; + if (m_UseCmbService) + { + // The CMB service will transfer ownership to another connected client + Assert.That(serverSideNewClientPlayer.OwnerClientId, Is.Not.EqualTo(newClientId), "Ownership should have been removed!"); + newOwner = GetOwningNetworkManager(serverSideNewClientPlayer); + } + Assert.That(serverSideNewClientPlayer.OwnerClientId, Is.EqualTo(newOwner.LocalClientId), "Ownership should have transferred to the authority!"); Assert.That(serverSideNewClientPlayer.IsPlayerObject, Is.True, $"{nameof(NetworkObject.IsPlayerObject)} should still be set!"); // Requesting the player's object after they've left should still while the object hasn't been destroyed - var playerObject = authority.SpawnManager.GetPlayerNetworkObject(newClientId); + var playerObject = newOwner.SpawnManager.GetPlayerNetworkObject(newClientId); Assert.That(playerObject != null, Is.True, "The authority should still be able to get the player object after the client has disconnected"); // Despawn and destroy the player object @@ -131,12 +142,26 @@ public IEnumerator TestPlayerPrefabConnectAndDisconnect([Values] DestroyWithOwne // Regression test: // check that the authority's player object isn't destroyed - Assert.That(authority.LocalClient.PlayerObject != null, Is.True, "The server's player object should not have been destroyed!"); + Assert.That(newOwner.LocalClient.PlayerObject != null, Is.True, "The server's player object should not have been destroyed!"); } // sanity check that requesting the object from the client who left after the object was destroyed is now null var sanity = authority.SpawnManager.GetPlayerNetworkObject(newClientId); Assert.Null(sanity, $"{nameof(NetworkSpawnManager.GetPlayerNetworkObject)} shouldn't be able to get the player object after the client has disconnected!"); } + + private NetworkManager GetOwningNetworkManager(NetworkObject networkObject) + { + foreach (var manager in m_NetworkManagers) + { + if (manager.LocalClientId == networkObject.OwnerClientId) + { + return manager; + } + } + Assert.Fail($"Failed to find network manager who owns object {networkObject.name}. OwnerClientId: {networkObject.OwnerClientId}"); + return null; + } } + } From 329762a1faa5d7cda032b72e23267689af4df551 Mon Sep 17 00:00:00 2001 From: Emma Date: Fri, 24 Apr 2026 14:27:54 -0400 Subject: [PATCH 15/16] fixes based off of code review --- .../Runtime/Logging/ContextualLogger.cs | 19 ++++++---- .../Runtime/Logging/NetworkLog.cs | 35 +++++++++++-------- .../NetworkObjectDestroyTests.cs | 2 +- .../TestHelpers/NetcodeIntegrationTest.cs | 3 +- 4 files changed, 36 insertions(+), 23 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs b/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs index 0ea2f5d858..5359ded027 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs @@ -18,15 +18,20 @@ namespace Unity.Netcode.Logging /// internal class ContextualLogger { + private const string k_CompilationCondition = "UNITY_ASSERTIONS"; + private const string k_NetcodeHeader = "[Netcode] "; - private readonly bool m_UseCompatibilityMode; private readonly Object m_Object; private readonly LogBuilder m_Builder = new(); private LogContextNetworkManager m_ManagerContext; private readonly GenericContext m_LoggerContext; - private const string k_CompilationCondition = "UNITY_ASSERTIONS"; + /// + /// Compatibility mode with the old behavior of NetworkLog + /// TODO: remove this when enough of the codebase is using the new log system + /// + private readonly bool m_UseCompatibilityMode; /// /// Creates a minimally configured contextual logger @@ -54,11 +59,13 @@ public ContextualLogger(Object inspectorObject, [NotNull] NetworkManager network m_LoggerContext = GenericContext.Create(); } - [Conditional(k_CompilationCondition)] - internal void UpdateNetworkManagerContext(NetworkManager manager) + /// Used for the NetworkLog + internal ContextualLogger(NetworkManager networkManager, bool useCompatibilityMode) { - m_ManagerContext.Dispose(); - m_ManagerContext = new LogContextNetworkManager(manager); + m_UseCompatibilityMode = useCompatibilityMode; + m_ManagerContext = new LogContextNetworkManager(networkManager); + m_Object = networkManager; + m_LoggerContext = GenericContext.Create(); } [Conditional(k_CompilationCondition)] diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs b/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs index 11266b58fd..29a092e77d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs @@ -17,17 +17,24 @@ internal struct LogConfiguration public static class NetworkLog { internal static LogConfiguration Config; - private static readonly ContextualLogger k_Log = new(true); + private static ContextualLogger s_Log = new(true); - internal static void SetNetworkManager(NetworkManager networkManager) + /// + /// Configures the NetworkLog for integration tests. + /// + internal static void ConfigureIntegrationTestLogging(NetworkManager networkManager, bool enableVerboseDebug = false) { - k_Log.UpdateNetworkManagerContext(networkManager); + // useCompatibilityMode when verboseDebug is not enabled + s_Log = new ContextualLogger(networkManager, !enableVerboseDebug); + // This setting will do nothing if the logger is created with useCompatibilityMode=true + Config.LogNetworkManagerRole = enableVerboseDebug; } + /// /// Gets the current log level. /// /// The current log level. - // [Obsolete("Use the LogLevel directly on the NetworkManager instead")] + // TODO: Work on deprecating this field. public static LogLevel CurrentLogLevel => NetworkManager.Singleton == null ? LogLevel.Normal : NetworkManager.Singleton.LogLevel; // internal logging @@ -37,27 +44,27 @@ internal static void SetNetworkManager(NetworkManager networkManager) /// /// The message to log [HideInCallstack] - public static void LogInfo(string message) => k_Log.Info(new Context(LogLevel.Normal, message, true)); + public static void LogInfo(string message) => s_Log.Info(new Context(LogLevel.Normal, message, true)); [HideInCallstack] - internal static void LogInfo(Context context) => k_Log.Info(context); + internal static void LogInfo(Context context) => s_Log.Info(context); /// /// Locally logs a warning log with Netcode prefixing. /// /// The message to log [HideInCallstack] - public static void LogWarning(string message) => k_Log.Warning(new Context(LogLevel.Error, message, true)); + public static void LogWarning(string message) => s_Log.Warning(new Context(LogLevel.Error, message, true)); [HideInCallstack] - internal static void LogWarning(Context context) => k_Log.Warning(context); + internal static void LogWarning(Context context) => s_Log.Warning(context); /// /// Locally logs a error log with Netcode prefixing. /// /// The message to log [HideInCallstack] - public static void LogError(string message) => k_Log.Error(new Context(LogLevel.Error, message, true)); + public static void LogError(string message) => s_Log.Error(new Context(LogLevel.Error, message, true)); [HideInCallstack] - internal static void LogError(Context context) => k_Log.Error(context); + internal static void LogError(Context context) => s_Log.Error(context); // internal static void Log(LogLevel level, object message, Object gameObject) => Logger.Log($"[Netcode] {message} ({(int)level})"); @@ -66,28 +73,28 @@ internal static void SetNetworkManager(NetworkManager networkManager) /// /// The message to log [HideInCallstack] - public static void LogInfoServer(string message) => k_Log.InfoServer(new Context(LogLevel.Normal, message, true)); + public static void LogInfoServer(string message) => s_Log.InfoServer(new Context(LogLevel.Normal, message, true)); /// /// Logs an info log locally and on the session owner if possible. /// /// The message to log [HideInCallstack] - public static void LogInfoSessionOwner(string message) => k_Log.InfoServer(new Context(LogLevel.Normal, message, true)); + public static void LogInfoSessionOwner(string message) => s_Log.InfoServer(new Context(LogLevel.Normal, message, true)); /// /// Logs a warning log locally and on the server if possible. /// /// The message to log [HideInCallstack] - public static void LogWarningServer(string message) => k_Log.WarningServer(new Context(LogLevel.Error, message, true)); + public static void LogWarningServer(string message) => s_Log.WarningServer(new Context(LogLevel.Error, message, true)); /// /// Logs an error log locally and on the server if possible. /// /// The message to log [HideInCallstack] - public static void LogErrorServer(string message) => k_Log.ErrorServer(new Context(LogLevel.Error, message, true)); + public static void LogErrorServer(string message) => s_Log.ErrorServer(new Context(LogLevel.Error, message, true)); internal static LogType GetMessageLogType(UnityEngine.LogType engineLogType) { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs index 37ce83bb96..0e226ec115 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs @@ -152,7 +152,7 @@ public IEnumerator TestNetworkObjectClientDestroy([Values] ClientDestroyObject c // The non-authority client is =NOT= allowed to destroy any spawned object it does not // have authority over during runtime. LogAssert.ignoreFailingMessages = true; - NetworkLog.SetNetworkManager(nonAuthorityClient); + NetworkLog.ConfigureIntegrationTestLogging(nonAuthorityClient); Object.Destroy(clientPlayerClone.gameObject); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs index 2deb5b68c6..fe22bdeb52 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs @@ -594,7 +594,6 @@ private void InternalOnOneTimeSetup() IntegrationTestSceneHandler.VerboseDebugMode = m_EnableVerboseDebug; NetworkManagerHelper.VerboseDebugMode = m_EnableVerboseDebug; VerboseDebug($"Entering {nameof(OneTimeSetup)}"); - // NetworkLog.Config.LogNetworkManagerRole = true; m_NetworkManagerInstatiationMode = OnSetIntegrationTestMode(); @@ -809,7 +808,7 @@ protected void CreateServerAndClients(int numberOfClients) m_NumberOfClients = numberOfClients; m_ClientNetworkManagers = clients; m_ServerNetworkManager = server; - NetworkLog.SetNetworkManager(server); + NetworkLog.ConfigureIntegrationTestLogging(server, m_EnableVerboseDebug); var managers = clients.ToList(); if (!m_UseCmbService) From 1addd810108a3073ad55e5e53e9821716fdc4f09 Mon Sep 17 00:00:00 2001 From: Emma Date: Fri, 24 Apr 2026 16:48:39 -0400 Subject: [PATCH 16/16] Fix vetting test --- com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs | 4 ++-- .../Runtime/Logging/ContextualLogger.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index a1ec92c33a..b9a2c15789 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1054,7 +1054,7 @@ private void Awake() { if (Log == null) { - Log = new ContextualLogger(this, this); + Log = new ContextualLogger(this, false); } NetworkConfig?.InitializePrefabs(); @@ -1824,7 +1824,7 @@ internal void OnValidate() if (Log == null) { - Log = new ContextualLogger(this, this); + Log = new ContextualLogger(this, false); } // Do a validation pass on NetworkConfig properties diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs b/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs index 5359ded027..006f919ce1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs @@ -60,7 +60,7 @@ public ContextualLogger(Object inspectorObject, [NotNull] NetworkManager network } /// Used for the NetworkLog - internal ContextualLogger(NetworkManager networkManager, bool useCompatibilityMode) + internal ContextualLogger(NetworkManager networkManager, bool useCompatibilityMode) { m_UseCompatibilityMode = useCompatibilityMode; m_ManagerContext = new LogContextNetworkManager(networkManager);