Metadata

Organizing Logs

Use categories, tags, headers, aliases, and scopes so rows stay readable and filters stay useful.

  • Project metadata facade
  • Categories
  • Tags and tag groups
  • Aliases, headers, and scopes

Choose the Right Metadata

Azkar Console gives you four practical metadata tools. They solve different problems and can be combined through LogOptions or a scope.

ToolBest ForExample
CategoryOne broad system ownerNetwork, Gameplay, UI
TagCross-cutting labels, often multiple per logMultiplayer, Boss, Performance
HeaderA visible prefix that improves scanning[Inventory] Item added
AliasA named instance or rolePlayer 1, Server, Boss AI

Recommended Catalog Pattern

For project-specific categories, tags, headers, aliases, tag groups, and option presets, create your own facade instead of extending AzCon. A shared facade keeps call sites short, avoids typo-driven string drift, centralizes colors and descriptions, and gives the Filter Builder a project vocabulary users can trust.

using Azkar.Console;
using UnityEngine;

public static class GameLog
{
    public static class Category
    {
        public static readonly LogCategoryEntry Inventory =
            AzCon.Category.Create("Inventory", new Color(0.35f, 0.78f, 0.90f));

        public static readonly LogCategoryEntry Quest =
            AzCon.Category.Create("Quest", new Color(0.95f, 0.71f, 0.28f));
    }

    public static class Tag
    {
        public static readonly LogTagEntry Loot = AzCon.Tag.Create("Loot");
        public static readonly LogTagEntry Important = AzCon.Tag.Create("Important");
    }

    public static class Header
    {
        public static readonly LogHeader Inventory =
            AzCon.Header.Create("Inventory", new Color(0.35f, 0.78f, 0.90f));
    }

    public static class Alias
    {
        public static readonly LogAlias InventorySystem =
            AzCon.Alias.Create("InventorySystem", new Color(0.35f, 0.78f, 0.90f));
    }

    public static class Group
    {
        public static readonly TagMask LootFlow =
            AzCon.TagGroup.Register("LootFlow", Tag.Loot, Tag.Important);
    }

    public static class Options
    {
        public static readonly LogOptions InventoryLoot = new LogOptions(
            category: Category.Inventory,
            tags: Group.LootFlow,
            header: Header.Inventory,
            alias: Alias.InventorySystem);
    }
}

Then use the facade from your gameplay code.

AzCon.Log("Item equipped", in GameLog.Options.InventoryLoot);
AzCon.Log("Quest accepted", category: GameLog.Category.Quest);

Do not declare a partial AzCon facade from regular Unity game scripts. The package facade is compiled into the Azkar.Console assembly definition. A partial AzCon in Assembly-CSharp creates a second Azkar.Console.AzCon type and shadows the real API instead of extending it. Runtime creation is still useful for real runtime identities, such as player slots, remote clients, generated modules, and spawned entities.

Categories

Every log has one category. Use categories for broad ownership and quick filtering, then use tags for additional dimensions.

AzCon.Category.Default
AzCon.Category.Unity
AzCon.Category.Gameplay
AzCon.Category.UI
AzCon.Category.Network
AzCon.Category.Audio
AzCon.Category.Physics
AzCon.Category.AI
AzCon.Category.Animation
AzCon.Category.Input
AzCon.Category.Save
AzCon.Category.Performance
AzCon.Category.Events
AzCon.Category.Assert
AzCon.Category.Compiler

Use built-in categories directly on logs when one value is enough.

AzCon.Log("Player spawned", AzCon.Category.Gameplay);
AzCon.LogWarning("Relay timeout", AzCon.Category.Network);
AzCon.LogError("Budget exceeded", AzCon.Category.Performance);

Create a project category when a system deserves its own filter identity.

private static readonly LogCategoryEntry InventoryCategory =
    AzCon.Category.Create("Inventory", new Color(0.35f, 0.78f, 0.90f));

var moduleCategory = AzCon.Category.GetOrCreate(moduleName, Color.cyan);

if (AzCon.Category.TryGet("Inventory", out var inventoryCategory))
{
    AzCon.Log("Inventory category exists", inventoryCategory);
}

Create(name, color) is idempotent by name and may update the color for an existing category while keeping the id stable. GetOrCreate(name, fallbackColor) is safer for dynamic code that should not intentionally recolor an existing category. TryGet looks up a category without creating one.

Tags

Use tags when one log belongs to more than one concern. Tags are a finite, high-speed classification layer for stable facets you expect to reuse across many logs.

Azkar Console supports up to 128 unique tag names in a project session. Tag names are case-insensitive, so Combat, combat, and COMBAT all resolve to the same tag. Each log stores tags as a 128-bit mask, which keeps tag filtering allocation-free and fast even in large sessions. If a project tries to create the 129th unique tag, Azkar Console rejects it instead of silently misclassifying logs.

private static readonly LogTagEntry MultiplayerTag = AzCon.Tag.Create("Multiplayer");
private static readonly LogTagEntry BossTag = AzCon.Tag.Create("Boss");
private static readonly LogTagEntry VerboseTag = AzCon.Tag.Create("Verbose");

TagMask oneTag = AzCon.Tags(BossTag);
TagMask manyTags = AzCon.Tags(MultiplayerTag, BossTag);
TagMask byName = AzCon.Tag.MaskFromNames("Multiplayer", "Boss");
TagMask withOperators = MultiplayerTag | BossTag;
TagMask incremental = TagMask.Empty.Add(MultiplayerTag).Add(BossTag);

AzCon.Log(
    "Boss replicated state changed",
    manyTags);

Good tag candidates include:

  • Features or systems that cut across categories, such as Combat, Loot, Save, Networking, or AI.
  • Runtime modes or sides, such as Client, Server, Host, Editor, or Remote.
  • Investigation labels, such as Important, Retry, Config, Performance, or Security.
  • Workflow groupings, such as Export, Trace, SmokeTest, or Tutorial.

Avoid creating a new tag for every object, request, quest id, item name, player id, generated value, or one-off event. Those labels can burn through the fixed tag space and are harder to filter consistently. Use categories for the primary owner, aliases for emitters or subsystems, headers for visual grouping, context for Unity objects, source filters for callsites, and the log message for dynamic values.

Use tag groups for combinations your team filters repeatedly.

TagMask combatNetwork = AzCon.TagGroup.Register(
    "CombatNetwork",
    MultiplayerTag,
    BossTag);

AzCon.Log("Combat packet received", combatNetwork);

AzCon.Tag.TryGet(name, out tag) looks up a tag without creating it. Use MaskFromNames(...) when a bounded, stable vocabulary should create any missing tags and return a mask. Tag groups let you reuse common combinations without spending extra tag slots.

Headers

Headers are visible prefixes. Use them when the row should be scannable at a glance.

AzCon.Log("Handshake started", AzCon.Header.Network);

Create reusable or temporary headers as needed.

private static readonly LogHeader InventoryHeader =
    AzCon.Header.Create("Inventory", new Color(0.4f, 0.8f, 0.6f));

AzCon.Log("Item equipped", InventoryHeader);
AzCon.Log("Checkpoint reached", AzCon.Header.Quick("TEMP", Color.yellow));

Use a header scope when a block should share the same visual prefix.

using (AzCon.HeaderScope("Inventory", Color.cyan))
{
    AzCon.Log("Equipped item");
    AzCon.Debug("Slot cache rebuilt");
}

Aliases

Aliases name an instance, role, or logical source. They are useful when repeated logs come from many similar objects.

private static readonly LogAlias ServerAlias =
    AzCon.Alias.Create("Server", new Color(0.9f, 0.6f, 0.1f));

AzCon.Log("Session started", ServerAlias);

Dynamic aliases are useful for runtime identities.

LogAlias playerAlias = AzCon.Alias.Dynamic($"Player {playerId}");

AzCon.TraceAlias("Move applied", playerAlias);

Static aliases are for known systems and dynamic aliases are for runtime identities. AzCon.Alias.TryGet(name, out alias) looks up without creating, and AzCon.Alias.GetEntry(alias) resolves display metadata such as name, color, and description.

Scopes

Scopes apply shared metadata to every log inside a block. Explicit per-call metadata takes precedence, while tags are combined.

private static readonly LogTagEntry RelayTag = AzCon.Tag.Create("Relay");

var scope = new LogScopeOptions(
    category: AzCon.Category.Network,
    tags: AzCon.Tags(RelayTag),
    header: AzCon.Header.Network,
    alias: AzCon.Alias.Dynamic("Client"),
    context: this);

using (AzCon.Scope(in scope))
{
    AzCon.Log("Connecting to relay");
    AzCon.LogWarning("Relay RTT spike detected");
}

Use scopes when repeating the same metadata would make logging code harder to read. Scopes are thread-local, so they do not automatically cross async or thread boundaries.