Ejemplo n.º 1
0
 public Log(string name, string prefix, params ILogOutput[] outputs)
 {
     _name            = name;
     _prefix          = prefix;
     _formattedPrefix = LogFormat.LogPrefix(prefix);
     _outputs         = new List <ILogOutput>(outputs);
 }
Ejemplo n.º 2
0
        public static bool TryFormat(object obj, out string formattedString)
        {
            if (obj is UnityEngine.Object)
            {
                formattedString = LogFormat.UnityObject(obj as UnityEngine.Object);
            }
            else if (obj is Exception)
            {
                formattedString = LogFormat.Exception(obj as Exception);
            }
            else if (obj is MemberInfo)
            {
                formattedString = LogFormat.MemberInfo(obj as MemberInfo);
            }
            else if (obj is Assembly)
            {
                formattedString = LogFormat.Assembly(obj as Assembly);
            }
            else if (obj is StackFrame)
            {
                formattedString = LogFormat.StackFrame(obj as StackFrame);
            }
            else
            {
                formattedString = null;
                return(false);
            }

            return(true);
        }
Ejemplo n.º 3
0
 private static void AppDomain_AssemblyLoad(object sender, AssemblyLoadEventArgs args)
 {
     Logs.System.Write(
         "Assembly Loaded: {0} in AppDomain {1}",
         LogFormat.Assembly(args.LoadedAssembly),
         LogFormat.AppDomain(AppDomain.CurrentDomain));
 }
Ejemplo n.º 4
0
        /// <summary>
        /// Initializes a global logfile using the specified target directory and file name
        /// using default settings. If not specified otherwise, a Logs directory is used and
        /// the log file's name is derived from the current <see cref="DateTime"/>.
        /// </summary>
        public static TextWriterLogOutput InitGlobalLogFile(string directory = null, string fileName = null, TextLogOutputConfig config = null)
        {
            // In case someone calls this multiple times, shut down the old one
            ShutdownTextLog();

            try
            {
                // Open a writable stream to the desired, default or fallback log file location
                string loggingPath;
                if (!TryCreateLogStream(directory, fileName, out _textFileLogWriter, out loggingPath))
                {
                    Logs.Default.WriteWarning("Text Logfile unavailable, because no logging location was accessible.");
                    return(null);
                }

                config.LoggingPath = loggingPath;
                // Create, configure and register a log output using the log stream
                _textFileLogOutput = new TextWriterLogOutput(_textFileLogWriter, config);
                Logs.AddGlobalOutput(_textFileLogOutput);
                return(_textFileLogOutput);
            }
            catch (Exception e)
            {
                Logs.Default.WriteWarning("Failed to create text logfile: {0}", LogFormat.Exception(e));
                return(null);
            }
        }
Ejemplo n.º 5
0
        private static string FormatMessage(string format, object[] obj)
        {
            if (obj == null || obj.Length == 0)
            {
                return(format);
            }
            string msg;

            try
            {
                // Format unity objects, because their .ToString implementation isn't great
                // and if users do it manually, we lose the context object (because then it's a string).
                //
                // While we're at it, let's auto-transform some other objects with less-than-ideal
                // .ToString() implementation as well.
                object[] preFormatObj = new object[obj.Length];
                for (int i = 0; i < preFormatObj.Length; i++)
                {
                    string formattedObj;
                    if (LogFormat.TryFormat(obj[i], out formattedObj))
                    {
                        preFormatObj[i] = formattedObj;
                    }
                    else
                    {
                        preFormatObj[i] = obj[i];
                    }
                }

                // Format the actual message
                msg = string.Format(System.Globalization.CultureInfo.InvariantCulture, format, preFormatObj);
            }
            catch (Exception e)
            {
                // Don't allow log message formatting to throw unhandled exceptions,
                // because they would result in another log - and probably more exceptions.

                // Instead, embed format, arguments and the exception in the resulting
                // log message, so the user can retrieve all necessary information for
                // fixing his log call.
                msg = format + Environment.NewLine;
                if (obj != null)
                {
                    try
                    {
                        msg += obj.ToString(", ") + Environment.NewLine;
                    }
                    catch (Exception)
                    {
                        msg += "(Error in ToString call)" + Environment.NewLine;
                    }
                }
                msg += LogFormat.Exception(e);
            }
            return(msg);
        }
Ejemplo n.º 6
0
        public virtual void Write(Log source, LogEntry entry, object context)
        {
            string[] lines = entry.Message.Split(
                new[] { '\n', '\r', '\0' },
                StringSplitOptions.RemoveEmptyEntries);

            lock (_writerLock)
            {
                int           totalPrefixLength = 0;
                StringBuilder builder           = new StringBuilder();
                for (int i = 0; i < lines.Length; i++)
                {
                    builder.Length = 0;
                    if (i == 0)
                    {
                        if (_config.WriteTimeStamps)
                        {
                            builder.Append(entry.TimeStamp.ToString(_config.TimeStampFormat));
                            builder.Append(' ');
                        }
                        if (_config.WriteFrameStamps)
                        {
                            builder.AppendFormat(_config.FrameStampFormat, entry.FrameStamp);
                            builder.Append(' ');
                        }
                        if (source.FormattedPrefix != null)
                        {
                            builder.Append(source.FormattedPrefix);
                            builder.Append(' ');
                        }
                        builder.Append(_config.UseStandardTypeFormat ?
                                       LogFormat.LogMessageTypeStandard(entry.Type) :
                                       LogFormat.LogMessageTypeShort(entry.Type));
                        builder.Append(": ");
                        builder.Append(' ', _indent * 2);
                        totalPrefixLength = builder.Length;
                    }
                    else
                    {
                        builder.Append(' ', totalPrefixLength);
                    }
                    builder.Append(lines[i]);

                    lines[i] = builder.ToString();
                    WriteLine(entry, lines[i]);
                }
            }
        }
Ejemplo n.º 7
0
 public static void WriteLoadedAssemblies(Log log)
 {
     try
     {
         log.Write(
             "Currently Loaded Assemblies:" + Environment.NewLine + "{0}",
             AppDomain.CurrentDomain.GetAssemblies()
             .ToString(
                 assembly => string.Format("  {0}", LogFormat.Assembly(assembly)),
                 Environment.NewLine));
     }
     catch (Exception e)
     {
         log.WriteWarning("Error logging loaded assemblies: {0}", LogFormat.Exception(e));
     }
 }
Ejemplo n.º 8
0
        private static string RetrieveEditorContextInfo(object context)
        {
            // Do a stack trace in order to find one. Don't do this outside
            // the editor. It's too expensive and might not be supported on
            // some platforms
            //
            // We can skip two frames, since one is this one and the next
            // is definitely a Log method, since FindContext is private.
            System.Diagnostics.StackFrame stackFrame = null;
            try {
                System.Diagnostics.StackTrace   trace  = new System.Diagnostics.StackTrace(2);
                System.Diagnostics.StackFrame[] frames = trace.GetFrames();
                for (int i = 0; i < frames.Length; i++)
                {
                    System.Reflection.MethodBase method = frames[i].GetMethod();
                    Type type          = method.DeclaringType;
                    bool isLoggingType =
                        !string.IsNullOrEmpty(type.Namespace) &&
                        type.Namespace.StartsWith(typeof(Log).Namespace);

                    // Select the first stack frame that is not part of the
                    // logging code, which is defined as everything in the
                    // same namespace as the Log class
                    if (!isLoggingType)
                    {
                        stackFrame = frames[i];
                        break;
                    }
                }
            }
            catch (Exception) {}

            // Select what to display based on the kind of context provided
            if (stackFrame != null)
            {
                return(LogFormat.StackFrame(stackFrame));
            }
            else if (context is UnityEngine.Object)
            {
                return(LogFormat.UnityObject(context as UnityEngine.Object));
            }
            else
            {
                return(null);
            }
        }
Ejemplo n.º 9
0
 static StaticLogHolder()
 {
     try {
         T initializer = new T();
         Log = new Log(
             initializer.Name,
             initializer.Prefix,
             Logs.GlobalLogOutput.ToArray());
         initializer.InitLog(Log);
         Logs._customGlobalLogs.Add(Log);
     }
     catch (Exception e) {
         Log = new Log(string.Empty, string.Empty);
         Logs.Default.WriteError(
             "Error initializing custom Log '{0}': {1}",
             LogFormat.Type(typeof(T)),
             LogFormat.Exception(e));
     }
 }
Ejemplo n.º 10
0
        public static void WriteMemoryUsage(Log log)
        {
            if (UnityEngine.Profiling.Profiler.supported)
            {
                log.Write("Memory Usage (Unity Profiler):" + Environment.NewLine +
                          "  Mono Heap Size:         {0} MiB" + Environment.NewLine +
                          "  Mono Used Size:         {1} MiB" + Environment.NewLine +
                          "  Total Allocated Memory: {2} MiB" + Environment.NewLine +
                          "  Total Reserved Memory:  {3} MiB" + Environment.NewLine +
                          "  Unused Reserved Memory: {4} MiB" + Environment.NewLine +
                          "  Used Heap Size:         {5} MiB",
                    #if UNITY_5_6_OR_NEWER
                          UnityEngine.Profiling.Profiler.GetMonoHeapSizeLong() / 1024L / 1024L,
                          UnityEngine.Profiling.Profiler.GetMonoUsedSizeLong() / 1024L / 1024L,
                          UnityEngine.Profiling.Profiler.GetTotalAllocatedMemoryLong() / 1024L / 1024L,
                          UnityEngine.Profiling.Profiler.GetTotalReservedMemoryLong() / 1024L / 1024L,
                          UnityEngine.Profiling.Profiler.GetTotalUnusedReservedMemoryLong() / 1024L / 1024L,
                          UnityEngine.Profiling.Profiler.usedHeapSizeLong / 1024L / 1024L);
                    #else
                          UnityEngine.Profiling.Profiler.GetMonoHeapSize() / 1024L / 1024L,
                          UnityEngine.Profiling.Profiler.GetMonoUsedSize() / 1024L / 1024L,
                          UnityEngine.Profiling.Profiler.GetTotalAllocatedMemory() / 1024L / 1024L,
                          UnityEngine.Profiling.Profiler.GetTotalReservedMemory() / 1024L / 1024L,
                          UnityEngine.Profiling.Profiler.GetTotalUnusedReservedMemory() / 1024L / 1024L,
                          UnityEngine.Profiling.Profiler.usedHeapSize / 1024L / 1024L);
                    #endif
            }

            try
            {
                log.Write("Memory Usage (GC):" + Environment.NewLine +
                          "  Total Memory: {0} MiB" + Environment.NewLine +
                          "  Collections:  {1} | {2} | {3}",
                          GC.GetTotalMemory(false) / 1024L / 1024L,
                          GC.CollectionCount(0),
                          GC.CollectionCount(1),
                          GC.CollectionCount(2));
            }
            catch (Exception e)
            {
                log.WriteWarning("Error logging GC memory stats: {0}", LogFormat.Exception(e));
            }
        }
Ejemplo n.º 11
0
        public static void WriteEnvironmentVariables(Log log)
        {
            try
            {
                var machineVars = Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Machine);
                var userVars    = Environment.GetEnvironmentVariables(EnvironmentVariableTarget.User);
                var processVars = Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Process);

                log.Write(
                    "Machine Variables:" + Environment.NewLine + "{0}",
                    machineVars.Keys
                    .OfType <string>()
                    .Select(key => new KeyValuePair <string, object>(key, machineVars[key]))
                    .ToString(
                        pair => string.Format("  {0}: {1}", pair.Key, pair.Value),
                        Environment.NewLine));
                log.Write(
                    "User Variables:" + Environment.NewLine + "{0}",
                    userVars.Keys
                    .OfType <string>()
                    .Select(key => new KeyValuePair <string, object>(key, userVars[key]))
                    .ToString(
                        pair => string.Format("  {0}: {1}", pair.Key, pair.Value),
                        Environment.NewLine));
                log.Write(
                    "Process Variables:" + Environment.NewLine + "{0}",
                    processVars.Keys
                    .OfType <string>()
                    .Select(key => new KeyValuePair <string, object>(key, processVars[key]))
                    .ToString(
                        pair => string.Format("  {0}: {1}", pair.Key, pair.Value),
                        Environment.NewLine));
            }
            catch (Exception e)
            {
                log.WriteWarning("Error logging environment variables: {0}", LogFormat.Exception(e));
            }
        }
Ejemplo n.º 12
0
        static Logs()
        {
            AppDomain.CurrentDomain.ProcessExit  += AppDomain_ProcessExit;
            AppDomain.CurrentDomain.DomainUnload += AppDomain_DomainUnload;
            AppDomain.CurrentDomain.AssemblyLoad += AppDomain_AssemblyLoad;

            // Normally, we'd use this hook to log all exceptions that end up uncaught, but
            // in testing, this never triggered. It is likely that Unity simply catches all
            // exceptions already, so this would be redundant with regular unity error log
            // forwarding, which is already in place.
            // AppDomain.CurrentDomain.UnhandledException += AppDomain_UnhandledException;

            _systemLog  = new Log("System");
            _defaultLog = new Log("Default");
            _unityLog   = new Log("Unity");

            // Install a forwarder from Unity to our custom logs
            UnityLogIntegration.Init();

            // Add a global log output that forwards to the regular Unity log
            try
            {
                UnityDebugLogOutput forwardToUnity = new UnityDebugLogOutput();
                Logs.AddGlobalOutput(forwardToUnity);
            }
            catch (Exception e)
            {
                Logs.System.WriteWarning("Rerouting Logs to Unity Debug Logs failed: {0}", LogFormat.Exception(e));
            }
        }
Ejemplo n.º 13
0
        private static bool TryCreateLogStream(string path, out StreamWriter writer)
        {
            try
            {
                string directory = Path.GetDirectoryName(path);
                if (!Directory.Exists(directory))
                {
                    Directory.CreateDirectory(directory);
                }

                writer           = new StreamWriter(path);
                writer.AutoFlush = true;

                Logs.Default.Write("Created log stream at path '{0}'.", path);
                return(true);
            }
            catch (Exception e)
            {
                Logs.Default.WriteWarning("Failed to create log stream at path '{0}': {1}", path, LogFormat.Exception(e));
                writer = null;
                return(false);
            }
        }
Ejemplo n.º 14
0
        private static bool TryCreateLogStream(string preferredDir, string preferredName, out StreamWriter writer, out string loggingPath)
        {
            if (preferredDir == null)
            {
                preferredDir = DefaultLogFileDirectory;
            }
            if (preferredName == null)
            {
                preferredName = string.Format(DefaultLogFileNameFormat, DateTime.UtcNow).Replace(':', '-');
            }

            // Create a logfile at the desired path
            string logFilePath = Path.Combine(preferredDir, preferredName);

            if (TryCreateLogStream(logFilePath, out writer))
            {
                loggingPath = logFilePath;
                return(true);
            }

            // If that fails and it was a non-default path, try the default logfile path
            string defaultLogFilePath = Path.Combine(DefaultLogFileDirectory, preferredName);

            if (defaultLogFilePath != logFilePath &&
                TryCreateLogStream(defaultLogFilePath, out writer))
            {
                loggingPath = defaultLogFilePath;
                return(true);
            }

            // If that didn't work - for example due to security / permission issues - fall back
            // to a logfile path in Unity's persistent data path. This should be a writable location
            // in any case.
            string unityDataDir;

            try
            {
                // Unity paths are using forward slashes. We'll use GetFullPath to get a
                // normalized version so we can safely combine it without mixing path separators.
                unityDataDir = UnityEngine.Application.persistentDataPath;
                unityDataDir = Path.GetFullPath(unityDataDir);
            }
            catch (Exception e)
            {
                Logs.Default.WriteWarning("Unable to retrieve Unity persistent data path: {0}", LogFormat.Exception(e));
                loggingPath = null;
                return(false);
            }

            string altLogFilePath = Path.Combine(Path.Combine(unityDataDir, DefaultLogFileDirectory), preferredName);

            if (TryCreateLogStream(altLogFilePath, out writer))
            {
                loggingPath = altLogFilePath;
                return(true);
            }

            // If all failed, we probably can't create any file at all for some reason.
            loggingPath = null;
            return(false);
        }
Ejemplo n.º 15
0
        public static void WriteUnitySystemInfo(Log log)
        {
            try
            {
                log.Write(
                    "Unity Device SystemInfo:" + Environment.NewLine +
                    "  Type:  {0}" + Environment.NewLine +
                    "  Model: {1}" + Environment.NewLine +
                    "  Name:  {2}" + Environment.NewLine +
                    "  UUID:  {3}",
                    UnityEngine.SystemInfo.deviceType,
                    UnityEngine.SystemInfo.deviceModel,
                    UnityEngine.SystemInfo.deviceName,
                    UnityEngine.SystemInfo.deviceUniqueIdentifier);

                log.Write(
                    "Unity Device Feature SystemInfo:" + Environment.NewLine +
                    "  Audio:            {0}" + Environment.NewLine +
                    "  Gyroscope:        {1}" + Environment.NewLine +
                    "  Location Service: {2}" + Environment.NewLine +
                    "  Vibration:        {3}" + Environment.NewLine +
                    "  Accelerometer:    {4}",
                    UnityEngine.SystemInfo.supportsAudio,
                    UnityEngine.SystemInfo.supportsGyroscope,
                    UnityEngine.SystemInfo.supportsLocationService,
                    UnityEngine.SystemInfo.supportsVibration,
                    UnityEngine.SystemInfo.supportsAccelerometer);

                log.Write(
                    "Unity Machine / OS SystemInfo:" + Environment.NewLine +
                    "  OS:               {0}" + Environment.NewLine +
                    "  OS Family:        {1}" + Environment.NewLine +
                    "  Processor Type:   {2}" + Environment.NewLine +
                    "  Processor Count:  {3}" + Environment.NewLine +
                    "  Processor Freq.:  {4}" + Environment.NewLine +
                    "  Sys. Memory size: {5}",
                    UnityEngine.SystemInfo.operatingSystem,
                    UnityEngine.SystemInfo.operatingSystemFamily,
                    UnityEngine.SystemInfo.processorType,
                    UnityEngine.SystemInfo.processorCount,
                    UnityEngine.SystemInfo.processorFrequency,
                    UnityEngine.SystemInfo.systemMemorySize);

                log.Write(
                    "Unity Graphics SystemInfo:" + Environment.NewLine +
                    "  Device Type:    {0}" + Environment.NewLine +
                    "  Device Name:    {1}" + Environment.NewLine +
                    "  Device ID:      {2}" + Environment.NewLine +
                    "  Device Version: {3}" + Environment.NewLine +
                    "  Vendor:         {4}" + Environment.NewLine +
                    "  Vendor ID:      {5}",
                    UnityEngine.SystemInfo.graphicsDeviceType,
                    UnityEngine.SystemInfo.graphicsDeviceName,
                    UnityEngine.SystemInfo.graphicsDeviceID,
                    UnityEngine.SystemInfo.graphicsDeviceVersion,
                    UnityEngine.SystemInfo.graphicsDeviceVendor,
                    UnityEngine.SystemInfo.graphicsDeviceVendorID);

                Array textureFormats = Enum.GetValues(typeof(UnityEngine.TextureFormat));
                HashSet <UnityEngine.TextureFormat> supportedTextureFormats = new HashSet <UnityEngine.TextureFormat>();
                foreach (UnityEngine.TextureFormat format in textureFormats)
                {
                    try
                    {
                        if (UnityEngine.SystemInfo.SupportsTextureFormat(format))
                        {
                            supportedTextureFormats.Add(format);
                        }
                    }
                    catch (Exception) {}
                }

                Array renderTextureFormats = Enum.GetValues(typeof(UnityEngine.RenderTextureFormat));
                HashSet <UnityEngine.RenderTextureFormat> supportedRenderTextureFormats = new HashSet <UnityEngine.RenderTextureFormat>();
                foreach (UnityEngine.RenderTextureFormat format in renderTextureFormats)
                {
                    try
                    {
                        if (UnityEngine.SystemInfo.SupportsRenderTextureFormat(format))
                        {
                            supportedRenderTextureFormats.Add(format);
                        }
                    }
                    catch (Exception) {}
                }

                log.Write(
                    "Unity Graphics Feature SystemInfo:" + Environment.NewLine +
                    "  GPU Memory Size:     {0}" + Environment.NewLine +
                    "  Shader Level:        {1}" + Environment.NewLine +
                    "  Compute Shaders:     {2}" + Environment.NewLine +
                    "  Multi-Threaded:      {3}" + Environment.NewLine +
                    "  Reversed Z-Buffer:   {4}" + Environment.NewLine +
                    "  Max. Texture Size:   {5}" + Environment.NewLine +
                    "  NPOT Textures:       {6}" + Environment.NewLine +
                    "  CopyTexture:         {7}" + Environment.NewLine +
                    "  RenderToCubemap:     {8}" + Environment.NewLine +
                    "  RenderTarget Count:  {9}" + Environment.NewLine +
                    "  2D Array Textures:   {10}" + Environment.NewLine +
                    "  Cube Array Textures: {11}" + Environment.NewLine +
                    "  3D Textures:         {12}" + Environment.NewLine +
                    "  Sparse Textures:     {13}" + Environment.NewLine +
                    "  Instancing:          {14}" + Environment.NewLine +
                    "  Image Effects:       {15}" + Environment.NewLine +
                    "  Motion Vectors:      {16}" + Environment.NewLine +
                    "  Raw Shadow Depth:    {17}" + Environment.NewLine +
                    "  Shadows:             {18}" + Environment.NewLine +
                    "  RenderTex Formats:   {19}" + Environment.NewLine +
                    "  Texture Formats:     {20}",
                    UnityEngine.SystemInfo.graphicsMemorySize,
                    UnityEngine.SystemInfo.graphicsShaderLevel,
                    UnityEngine.SystemInfo.supportsComputeShaders,
                    UnityEngine.SystemInfo.graphicsMultiThreaded,
                    UnityEngine.SystemInfo.usesReversedZBuffer,
                    UnityEngine.SystemInfo.maxTextureSize,
                    UnityEngine.SystemInfo.npotSupport,
                    UnityEngine.SystemInfo.copyTextureSupport,
                    UnityEngine.SystemInfo.supportsRenderToCubemap,
                    UnityEngine.SystemInfo.supportedRenderTargetCount,
                    UnityEngine.SystemInfo.supports2DArrayTextures,
                    UnityEngine.SystemInfo.supportsCubemapArrayTextures,
                    UnityEngine.SystemInfo.supports3DTextures,
                    UnityEngine.SystemInfo.supportsSparseTextures,
                    UnityEngine.SystemInfo.supportsInstancing,
                    UnityEngine.SystemInfo.supportsImageEffects,
                    UnityEngine.SystemInfo.supportsMotionVectors,
                    UnityEngine.SystemInfo.supportsRawShadowDepthSampling,
                    UnityEngine.SystemInfo.supportsShadows,
                    supportedRenderTextureFormats.ToString(", "),
                    supportedTextureFormats.ToString(", "));
            }
            catch (Exception e)
            {
                log.WriteWarning("Error logging Unity system info: {0}", LogFormat.Exception(e));
            }
        }
Ejemplo n.º 16
0
        public static void WriteUnitySpecs(Log log)
        {
            try
            {
                log.Write(
                    "Unity Application paths:" + Environment.NewLine +
                    "  Persistent data:  {0}" + Environment.NewLine +
                    "  Application Data: {1}" + Environment.NewLine +
                    "  Temp data:        {2}" + Environment.NewLine +
                    "  Streaming Assets: {3}",
                    UnityEngine.Application.persistentDataPath,
                    UnityEngine.Application.dataPath,
                    UnityEngine.Application.temporaryCachePath,
                    UnityEngine.Application.streamingAssetsPath);

                log.Write(
                    "Unity Screen info:" + Environment.NewLine +
                    "  Output size:        {0}x{1}" + Environment.NewLine +
                    "  Fullscreen:         {2}" + Environment.NewLine +
                    "  Screen Resolution:  {3}" + Environment.NewLine +
                    "  Screen Orientation: {4}" + Environment.NewLine +
                    "  Screen DPI:         {5}",
                    UnityEngine.Screen.width,
                    UnityEngine.Screen.height,
                    UnityEngine.Screen.fullScreen,
                    UnityEngine.Screen.currentResolution,
                    UnityEngine.Screen.orientation,
                    UnityEngine.Screen.dpi);

                log.Write("Unity Display info:");
                log.PushIndent();
                for (int i = 0; i < UnityEngine.Display.displays.Length; i++)
                {
                    UnityEngine.Display display = UnityEngine.Display.displays[i];
                    log.Write(
                        "Display #{0} {1}" + Environment.NewLine +
                        "  Native size:    {2}x{3}" + Environment.NewLine +
                        "  Rendering size: {4}x{5}",
                        i,
                        display == UnityEngine.Display.main ? "(main)" : "",
                        display.systemWidth,
                        display.systemHeight,
                        display.renderingWidth,
                        display.renderingHeight);
                }
                log.PopIndent();

                UnityEngine.AudioConfiguration audioConfig = UnityEngine.AudioSettings.GetConfiguration();
                log.Write(
                    "Unity Audio Device info:" + Environment.NewLine +
                    "  Driver Caps:        {0}" + Environment.NewLine +
                    "  Speaker Mode:       {1}" + Environment.NewLine +
                    "  Output Sample Rate: {2}" + Environment.NewLine +
                    "  DSP Buffer Size:    {3}" + Environment.NewLine +
                    "  # Real Voices:      {4}" + Environment.NewLine +
                    "  # Virtual  Voices:  {5}",
                    UnityEngine.AudioSettings.driverCapabilities,
                    UnityEngine.AudioSettings.speakerMode,
                    UnityEngine.AudioSettings.outputSampleRate,
                    audioConfig.dspBufferSize,
                    audioConfig.numRealVoices,
                    audioConfig.numVirtualVoices);
            }
            catch (Exception e)
            {
                log.WriteWarning("Error logging Unity specs: {0}", LogFormat.Exception(e));
            }
        }