using System; using System.Collections.Generic; using System.Diagnostics; using UnityEngine; using UnityEngine.SceneManagement; using Debug = UnityEngine.Debug; namespace Unity.Multiplayer.Samples { public static class DedicatedServerUtilities { public static bool IsServerBuildTarget { get { #if UNITY_SERVER return true; #else return false; #endif } } /// /// Some notes on logging and dedicated servers: /// /// With dedicated server, you don't have a view into your game like you'd have with other types of platforms. You'll rely a lot on logging to get /// insights into what's happening on your servers, even more with your live fleets of thousands of servers. /// Unity's default logging isn't usable for dedicated server use cases. The following requirements are missing: /// - Structured logging: with 1000+ server fleets, you don't want to look at log files individually. You'll need log ingestion/analysis tools to be able /// to parse all that data (think tools like elasticsearch for example). Making your logs structured so they are machine friendly (for example /// having each log entry be a json object) makes this easier to integrate with those tools and easier to analyze. /// - Log levels: most of the time, you won't want to receive info or debug logs, only warnings and errors (so you don't spam your analysis tools and so they don't /// cost your a fortune). However, you'll also want to enable info and debug logs for specific servers when debugging them. Having a logger that /// manages this for you is better than wrapping your logs yourself and managing this yourself. /// - Log file rotation: Dedicated servers can run for days and days, while games on user devices will run for a few play sessions before being closed. This means your /// log file will grow and grow. Rotation is an automated way to swap log files each x hours or days. This allows deleting and easier managing /// of older log files. /// - Performance: logging can be a performance costly operation, which contributes to your CPU perf costs, which in turn are translated to hosting monetary costs. /// Having a logging library that's optimized for these scenarios is essential (burstable, threaded, etc). /// This also includes not having to print full stack traces (not needed for most devops operations, but could be enabled for debugging) /// /// A few solutions exists for this. ZLogger https://github.com/Cysharp/ZLogger and serilog https://www.nuget.org/packages/serilog/ for example. /// Unity also has an experimental package com.unity.logging that answers the above needs as well. Once this is out of experimental, this will be /// integrated in boss room. TODO for this first pass at DGS, we're using the default logger. TODO replace me - MTT-4038 /// /// public static void Log(string message) { // IMPORTANT FOR LOGGING READ ABOVE. The following isn't recommended for production use with dedicated game servers. Debug.Log($"[{DateTime.UtcNow}] {Time.realtimeSinceStartup} {Time.time} pid[{Process.GetCurrentProcess().Id}] - {message}"); } /// /// Quick tool to get insights in your current scene, to be able to debug client and server hierarchies. /// public static void PrintSceneHierarchy() { List rootObjects = new List(); Scene scene = SceneManager.GetActiveScene(); scene.GetRootGameObjects(rootObjects); string toPrint = "\n"; foreach (var rootObject in rootObjects) { toPrint += $"{GetInfoForObject(rootObject)}\n"; PrintChildObjectsRecursive(rootObject, depth: 0, ref toPrint); } Log(toPrint); } private static void PrintChildObjectsRecursive(GameObject parentObject, int depth, ref string toPrint) { if (parentObject.transform.childCount == 0) { return; } string tabs = new string(' ', ++depth * 4); foreach (Transform child in parentObject.transform) { toPrint += $"{tabs}{GetInfoForObject(child.gameObject)}\n"; PrintChildObjectsRecursive(child.gameObject, depth, ref toPrint); // asdf } } private static string GetInfoForObject(GameObject obj) { List allComponents = new(); obj.GetComponents(allComponents); var nullCount = allComponents.FindAll(component => component == null).Count; return $"{obj.name}\tnb null {nullCount}/{allComponents.Count}"; } } }