Last active
November 6, 2024 10:11
-
-
Save thygrrr/4ebe77dcd6a6c17cd9bcfb5a8c6d02d7 to your computer and use it in GitHub Desktop.
Unity Loggers, zero-boilerplate drop-in replacement logger in 25 lines of code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//SPDX-License-Identifier: Unlicense OR CC0-1.0+ | |
using System; | |
using System.Collections.Generic; | |
using UnityEngine; | |
using Object = UnityEngine.Object; | |
// ReSharper disable MemberCanBePrivate.Global | |
namespace Loggers | |
{ | |
/// <summary> | |
/// Inherit from this class to get a logger that prepends the name of the class to every log message. | |
/// <code>class Log : Loggers.Create<Log> {}</code> | |
/// </summary> | |
/// <remarks> | |
/// <para>Access methods statically: <c>Log.Info("Hello World");</c></para> | |
/// <para>Derive multiple classes with different purposes, and then import them in the files that use them.</para> | |
/// <para>You can also use a using declaration to name a specific logger for a class in the file you want to use it in.</para> | |
/// <para>You may add optional code to the class to add additional functionality that is exposed only for that type.</para> | |
///</remarks> | |
/// <example> | |
/// Derive a logger for a specific purpose: | |
/// <code>class MyGame.CameraLogger : Loggers.Create<CameraLogger> {}</code> | |
/// Import that specific logger: | |
/// <code>using Log = MyGame.CameraLogger;</code> | |
/// Define a Debug logger for a specific MonoBehaviour (can be done in the same file): | |
/// <code>using Log = Logging.Create<MyMonoBehaviour>;</code> | |
/// Shadowing UnityEngine.Debug, existing Debug.Log code calls the new logger: | |
/// <code>using Debug = MyGame.AnotherLoggingClass;</code> | |
/// </example> | |
/// <typeparam name="T">class whose name will be prepended to the log messages, can be the derived class itself</typeparam> | |
public abstract class Create<T> where T : class | |
{ | |
#region Public API | |
/// <summary> | |
/// Log a message to the Unity console at the Default Log Level. | |
/// Equivalent to Debug.Log(...), but the name of the Logging class will be prepended. | |
/// </summary> | |
/// <param name="message">string to log</param> | |
/// <param name="context">optional UnityEngine.Object that will be pinged when the user clicks the log message</param> | |
public static void Info(string message, Object context = null) => Logger.Log(LogType.Log, Tag, message, context); | |
/// <summary> | |
/// Log a message to the Unity console at the Warning Log Level. | |
/// Equivalent to Debug.LogWarning(...), but the name of the Logging class will be prepended. | |
/// </summary> | |
/// <param name="message">string to log</param> | |
/// <param name="context">optional UnityEngine.Object that will be pinged when the user clicks the log message</param> | |
public static void Warn(string message, Object context = null) => Logger.Log(LogType.Warning, Tag, message, context); | |
/// <summary> | |
/// Log a message to the Unity console at the Error Log Level. | |
/// Equivalent to Debug.LogError(...), but the name of the Logging class will be prepended. | |
/// </summary> | |
/// <param name="message">string to log</param> | |
/// <param name="context">optional UnityEngine.Object that will be pinged when the user clicks the log message</param> | |
public static void Error(string message, Object context = null) => Logger.Log(LogType.Error, Tag, message, context); | |
/// <summary> | |
/// Log an exception with stack trace to the Unity console. | |
/// Direct equivalent to Debug.LogException(...) | |
/// </summary> | |
/// <param name="ex">exception to output</param> | |
/// <param name="context">optional UnityEngine.Object that will be pinged when the user clicks the log message</param> | |
public static void Exception(Exception ex, Object context = null) => Logger.LogException(ex, context); | |
#endregion | |
#region Compatibility API | |
/// <summary> | |
/// Convenience accessor that mimics the Unity logger style. | |
/// See <see cref="Info"/> for details. | |
/// </summary> | |
public static void Log(string message, Object context = null) => Info(message, context); | |
/// <summary> | |
/// Convenience accessor that mimics the Unity logger style. | |
/// See <see cref="Warn"/> for details. | |
/// </summary> | |
public static void LogWarning(string message, Object context = null) => Warn(message, context); | |
/// <summary> | |
/// Convenience accessor that mimics the Unity logger style. | |
/// See <see cref="Error"/> for details. | |
/// </summary> | |
public static void LogError(string message, Object context = null) => Error(message, context); | |
/// <summary> | |
/// Convenience accessor that mimics the Unity logger style. | |
/// See <see cref="Exception"/> for details. | |
/// </summary> | |
public static void LogException(Exception ex, Object context = null) => Exception(ex, context); | |
#endregion | |
#region Private Fields and Intialization | |
/// <summary> | |
/// Constructor that ensures registration. | |
/// </summary> | |
static Create() | |
{ | |
Registry.Entries[FullTag] = Logger; | |
} | |
// We want this exact behaviour here - a new field for every specialized type | |
// ReSharper disable StaticMemberInGenericType | |
private static readonly string Tag = typeof(T).Name; | |
private static readonly string FullTag = typeof(T).FullName; | |
// ReSharper disable once StaticMemberInGenericType | |
/// <summary> | |
/// Access to the logger for this type, allows to configure the log level and enable/disable logging and more. | |
/// See <see cref="UnityEngine.ILogger"/> for details (advanced use cases only). | |
/// </summary> | |
public static readonly Logger Logger = new(Debug.unityLogger); | |
#endregion | |
} | |
/// <summary> | |
/// Gives access to all existing loggers, for runtime Loglevel configuration. | |
/// </summary> | |
public static class Registry | |
{ | |
#region Public API | |
/// <summary> | |
/// A dictionary of all loggers, keyed by their full type name. | |
/// Can be used to write a service that allows runtime configuration of log levels. | |
/// </summary> | |
/// <example> | |
/// <code> | |
/// Registry.All["MyGame.Log"].filterLogType = LogLevel.Debug; | |
/// Registry.All["MyGame.Log"].logEnabled = false; | |
/// </code> | |
/// <remarks> | |
/// You can use typeof(MyGame.Log).FullName to get the full type name in a refactoring-proof way. | |
/// You can use Registry.All.Keys to get a list of all loggers, to display them in a UI, etc. | |
/// </remarks> | |
/// </example> | |
public static IReadOnlyDictionary<string, ILogger> All => Entries; | |
/// <summary> | |
/// Internal use for Logging Framework. | |
/// </summary> | |
internal static readonly Dictionary<string, ILogger> Entries = new(); | |
#endregion | |
} | |
} | |
/* | |
Written by Tiger Blue in 2023 | |
This is free and unencumbered software released into the public domain. | |
Anyone is free to copy, modify, publish, use, compile, sell, or | |
distribute this software, either in source code form or as a compiled | |
binary, for any purpose, commercial or non-commercial, and by any | |
means. | |
In jurisdictions that recognize copyright laws, the author or authors | |
of this software dedicate any and all copyright interest in the | |
software to the public domain. We make this dedication for the benefit | |
of the public at large and to the detriment of our heirs and | |
successors. We intend this dedication to be an overt act of | |
relinquishment in perpetuity of all present and future rights to this | |
software under copyright law. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR | |
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | |
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
OTHER DEALINGS IN THE SOFTWARE. | |
For more information, please refer to <http://unlicense.org> | |
*/ | |
/* | |
At your discretion, you may interchangeably treat this code as CC0 licensed. | |
https://creativecommons.org/publicdomain/zero/1.0/legalcode | |
*/ |
//SPDX-License-Identifier: Unlicense OR CC0-1.0+
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Loggers {
public abstract class Create<T> where T : class {
public static void Info(string message, UnityEngine.Object context = null) => Logger.Log(LogType.Log, Tag, message, context);
public static void Warn(string message, UnityEngine.Object context = null) => Logger.Log(LogType.Warning, Tag, message, context);
public static void Error(string message, UnityEngine.Object context = null) => Logger.Log(LogType.Error, Tag, message, context);
public static void Exception(Exception ex, UnityEngine.Object context = null) => Logger.LogException(ex, context);
public static void Log(string message, UnityEngine.Object context = null) => Info(message, context);
public static void LogWarning(string message, UnityEngine.Object context = null) => Warn(message, context);
public static void LogError(string message, UnityEngine.Object context = null) => Error(message, context);
public static void LogException(Exception ex) => Exception(ex);
static Create() {Registry.Entries[FullTag] = Logger;}
private static readonly string Tag = typeof(T).Name;
private static readonly string FullTag = typeof(T).FullName;
public static readonly Logger Logger = new(Debug.unityLogger);
}
public static class Registry {
public static IReadOnlyDictionary<string, ILogger> All => Entries;
internal static readonly Dictionary<string, ILogger> Entries = new();
}
}
Still needs unit test etc, and some thorough testing past Unity's Domain Reloads (or lack thereof, based on Enter PlayMode Settings)
I'm optimistic this works, though.
Todo is adding colours, I've been thinking to interpret namespace or classname partially as a html/richtext color. That feels like a hack though, but could keep setup and boilerplate at zero.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Alternative License: CC0, Creative Commons Zero, if preferred.
25 line version (comments and whitespace stripped) below.