유니티 커스텀 로거 - 카테고리로 끄고 켜기

민트초코케밥·2025년 12월 23일

유니티 게임 개발

목록 보기
2/3

유니티 커스텀 로거

유니티로 개발할 때 가장 불편했던 점이 로그를 보는 것이었는데, 언제부턴가 이 로그들을 카테고라이징하고, 그걸 통해서 On/Off하고 싶었습니다.

요구사항:

카테고리별 On/Off: 던전 로그만 보고 싶을 때 나머지는 끈다
시각적 구분: 카테고리마다 색깔로 구분
중앙 관리: 프로젝트 전체에서 일관된 설정
간편한 사용: this.LogEx(카테고리, "메시지")

먼저 카테고리를 대충 만들어주고...

    public enum LogCategory
    {
        System,             
        Dungeon,            
        DungeonGeneration,  
        TerrainGeneration,  
        PropGeneration,     
        Monster,            
        Battle,             
        UI,                 
    }

설정으로 사용할 SO를 하나 만들어주었습니다.

public class LogSettingsSO : ScriptableObject
{
    [System.Serializable]
    public struct LogConfig
    {
        public bool enabled;
        public Color color;
    }

    public bool isMasterEnabled = true;
    public Dictionary<LogCategory, LogConfig> categories = new();

    public bool CheckCategory(LogCategory category, out Color color)
    {
        color = Color.white;
        if (!isMasterEnabled) return false;

        if (categories.TryGetValue(category, out var config))
        {
            color = config.color;
            return config.enabled;
        }
        return true;
    }
}

이제 이걸 사용해서 Logger를 짜면 됩니다. 일단 익스텐션 메서드로 만들어줍니다.

public static class LogExtensionLogger
{
    private readonly struct PrefixCacheEntry
    {
        public readonly int rgb24;
        public readonly string prefix;

        public PrefixCacheEntry(int rgb24, string prefix)
        {
            this.rgb24 = rgb24;
            this.prefix = prefix;
        }
    }

    private static readonly Dictionary<LogCategory, PrefixCacheEntry> _prefixCache = new();
    private static bool _warnedMissingSettings;

    private static int PackRgb24(Color c)
    {
        Color32 cc = c;
        return (cc.r << 16) | (cc.g << 8) | cc.b;
    }

    private static string GetOrBuildPrefix(LogCategory category, Color catColor)
    {
        int rgb24 = PackRgb24(catColor);

        if (_prefixCache.TryGetValue(category, out var entry) && entry.rgb24 == rgb24)
            return entry.prefix;

        string hex = ColorUtility.ToHtmlStringRGB(catColor);
        string prefix = $"<color=#{hex}><b>[{category}]</b></color>";

        _prefixCache[category] = new PrefixCacheEntry(rgb24, prefix);
        return prefix;
    }

    public static void LogEx(this object sender, LogCategory category, string key, object message)
    {
        LogSettingsSO settings = LogSettingsSO.Instance;

        if (settings == null)
        {
            if (!_warnedMissingSettings)
            {
                _warnedMissingSettings = true;
                Debug.LogWarning("[LogEx] LogSettings.asset is missing! Make sure it's in a 'Resources' folder.");
            }
            return;
        }

        if (!settings.CheckCategory(category, out Color catColor)) return;
        if (sender is ILoggable loggable && !loggable.EnableLog) return;

        string prefix = GetOrBuildPrefix(category, catColor);
        string keyStr = string.IsNullOrEmpty(key) ? string.Empty : $" <color=#808080>[{key}]</color>";

        UnityEngine.Object context = sender as UnityEngine.Object;
        Debug.Log($"{prefix}{keyStr} {message}", context);
    }

    public static void LogEx(this object sender, LogCategory category, object message)
        => LogEx(sender, category, string.Empty, message);

    public static void ClearLogPrefixCache()
    {
        _prefixCache.Clear();
        _warnedMissingSettings = false;
    }
}

호출은 이렇게 합니다.

public class DungeonGenerator : MonoBehaviour
{
    public void Generate()
    {
        this.LogEx(LogCategory.DungeonGeneration, "Started", "Begin generation");
        this.LogEx(LogCategory.DungeonGeneration, "Step", "Placing tile at (5,3)");
    }
}

콘솔에선

  • [DungeonGeneration][Started] Begin generation
  • [DungeonGeneration][Step] Placing tile at (5,3)

이런 식으로 나오게 되고 로거 내부에선...

    // 카테고리 체크 - 꺼져있으면 여기서 리턴
    if (!settings.CheckCategory(category, out Color catColor)) 
        return;
    
    // 통과한 것만 출력
    string prefix = GetOrBuildPrefix(category, catColor);
	Debug.Log($"{prefix}[{key}] {message}");
}

LogSettings에서 꺼둔 카테고리는 코드 실행조차 안 됩니다.

아래는 그냥 이쁘고 시인성 좋게...

private static string GetOrBuildPrefix(LogCategory category, Color catColor)
{
    int rgb24 = PackRgb24(catColor);

    if (_prefixCache.TryGetValue(category, out var entry) && entry.rgb24 == rgb24)
        return entry.prefix;

    string hex = ColorUtility.ToHtmlStringRGB(catColor);
    string prefix = $"<color=#{hex}><b>[{category}]</b></color>";

    _prefixCache[category] = new PrefixCacheEntry(rgb24, prefix);
    return prefix;
}

로거 바이패스

만족스러워 하던 도중 문제가 하나 있었는데, 콘솔에서 로그를 더블 클릭하면 LogEx를 호출한 위치로 가는 것이 아닌, LogEx 코드로 가버리는 문제였습니다. 내용에 있는 라인을 직접 클릭하면 문제 없었지만 뭔가 아쉬운 느낌이라 해결책을 찾아보았습니다.

찾아보니... 방법은 간단했습니다. 뭔가 어둠의 우회로 같은 느낌이긴 하지만 다음 조건으로 커스텀 로거를 바이패스하는 방법이 있었습니다.

  • 클래스 이름이 Logger로 끝남 + 메서드 이름이 Log로 시작

이렇게 구성하면 이상하게도 커스텀 로거가 바이패스되고 호출 위치로 데려다줍니다. 아마 내부 휴리스틱 로직에 얻어걸리는 듯합니다.


오딘을 쓰면 이렇게 이쁘게 나옵니다. 그러니까 오딘 쓰세요. 두 번 쓰세요.

profile
민트초코시렁

0개의 댓글