TheBestLogger Integration Best Practices
May 3, 2026 ยท View on GitHub
This guide is for teams integrating com.foxsterdev.thebestlogger into a real Unity project and trying to keep three things balanced:
- correct runtime logging
- low main-thread overhead
- safe production behavior under exceptions, threading, batching, and remote delivery
Core production rule:
- the logger should observe failures, format them, and route them to healthy targets
- the logger should not become a new source of runtime exceptions in gameplay, startup, shutdown, or crash-reporting paths
Use the package README for the package surface and release info:
Integration Profiles
Simple Integration
Use this profile when you need a fast, maintainable integration with low operational complexity.
Recommended shape:
- initialize
LogManageronce from the Unity main thread - keep the target set small
- keep categories stable and human-readable
- keep stack traces enabled mainly for
ErrorandException - avoid enabling every optional log source unless you actually need it
- keep
OpenSearchor other remote delivery out of the first rollout unless you already operate that pipeline
Typical target set:
UnityEditorConsoleLogTargetfor editor visibility- one platform-native target for build visibility:
AndroidSystemLogTargetAppleSystemLogTarget
Typical bootstrap shape:
using System.Collections.Generic;
using System.Threading;
using TheBestLogger;
using UnityEngine;
public static class LoggerBootstrap
{
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
public static void Initialize()
{
var logTargets = new List<LogTarget>
{
#if UNITY_EDITOR
new UnityEditorConsoleLogTarget(),
#endif
#if UNITY_ANDROID
new AndroidSystemLogTarget(Application.identifier),
#elif UNITY_IOS || UNITY_TVOS || UNITY_STANDALONE_OSX
new AppleSystemLogTarget(Application.identifier, "Unity"),
#endif
};
var disposingToken = CancellationToken.None;
#if UNITY_2022_3_OR_NEWER
disposingToken = Application.exitCancellationToken;
#endif
LogManager.Initialize(logTargets.AsReadOnly(), "GameLogger/Dev/", disposingToken);
}
}
Note:
- in non-Android and non-Apple runtime builds, replace the platform branch with your own runtime-capable target so the target list is never empty at initialization time
Why this profile is safe:
LogManager.Initialize(...)is designed to run on the Unity main thread- the package resolves
LogManagerConfigurationfromResources - category loggers are cached by
LogManager.CreateLogger(...) - default stack-trace policy already favors
ErrorandException
Scripted Integration Without Resources Paths
Use this profile when you want the bootstrap to stay fully in code and avoid implicit Resources.Load(...) dependencies, while still using the package's real public configuration types.
Typical bootstrap shape:
using System.Threading;
using StabilityHub;
using StabilityHub.Monitoring;
using TheBestLogger;
using TheBestLogger.Examples;
using UnityEngine;
public static class LoggerBootstrap
{
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
public static void Initialize()
{
var logTargets = new LogTarget[]
{
#if UNITY_EDITOR
new UnityEditorConsoleLogTarget(),
#endif
#if UNITY_ANDROID
new AndroidSystemLogTarget(Application.identifier),
#elif UNITY_IOS || UNITY_TVOS || UNITY_STANDALONE_OSX
new AppleSystemLogTarget(Application.identifier, "Unity"),
#endif
};
var appleConfigurationSo = ScriptableObject.CreateInstance<AppleSystemLogTargetConfigurationSO>();
appleConfigurationSo.SpecificConfiguration = new AppleSystemLogTargetConfiguration
{
MinLogLevel = LogLevel.Warning,
IsThreadSafe = true,
DebugMode = new DebugModeConfiguration(),
BatchLogs = new LogTargetBatchLogsConfiguration(),
DispatchingLogsToMainThread = new LogTargetDispatchingLogsToMainThreadConfiguration()
};
var configuration = LogManagerConfigurationPresets.CreateProduction(appleConfigurationSo);
#if UNITY_ANDROID
configuration.DebugUnityLoggerFilterLogType = LogType.Warning;
#elif UNITY_IOS || UNITY_TVOS || UNITY_STANDALONE_OSX
appleConfigurationSo.SpecificConfiguration.MinLogLevel = LogLevel.Error;
#endif
var disposingToken = CancellationToken.None;
#if UNITY_2022_3_OR_NEWER
disposingToken = Application.exitCancellationToken;
#endif
LogManager.Initialize(logTargets, configuration, disposingToken);
StabilityHubService.Initialize(LogManager.CreateLogger("Stability"), MonitoringConfigurationPresets.CreateProduction());
}
}
Why this profile is useful:
- you can start from
ProductionorQapresets - you can apply point overrides directly on the real config objects before initialization
- bootstrap does not depend on
Resourcesfolder layout - the old
ScriptableObjectpath still remains available for asset-driven projects
If you want a consumer-facing reference implementation, the demo scene in DevAccelerationSystem.DemoProject/Assets/TheBestLoggerSample/Scenes/LoggerSampleScene.unity now exposes this profile through a runtime tabbed UI. It lets you switch bootstrap modes, emit representative logs, patch target configs live, exercise StabilityHub, and inspect a safe mock OpenSearch flow without wiring a real remote endpoint.
Pro Integration
Use this profile when the project already has:
- background-thread logging
- remote log ingestion
- environment-specific configuration
- crash or previous-session recovery flows
- strict performance and validation requirements
Recommended shape:
- separate local visibility targets from remote delivery targets
- enable batching for expensive or remote sinks
- use main-thread dispatch only for targets that are not thread-safe
- use
DebugModeConfigurationfor scoped diagnostic amplification instead of permanently verbose logs - keep consumer validation and device/runtime proof as separate release evidence
- treat
OpenSearchandStabilityHubas production-sensitive surfaces
Typical target mix:
UnityEditorConsoleLogTargetfor editor/dev diagnostics- platform-native target for device-local observability
- remote target such as
OpenSearchLogTarget - optional runtime viewer or custom guarded third-party target
Configuration Rules
1. Bootstrap Rules
- Call
LogManager.Initialize(...)once. - Call it from the Unity main thread.
- Pass a non-empty target list.
- Point
resourceSubFolderThatContainsConfigsto theResourcessubfolder that containsLogManagerConfiguration. - Use
Application.exitCancellationTokenon supported Unity versions so logger disposal is wired into shutdown. - Do not call
LogManager.CreateLogger(...)before initialization. - Do not create loggers from constructors, static field initializers, or other early bootstrap paths that can run before logger initialization.
- If a startup system needs a logger, resolve it lazily after initialization or inject it after the logger bootstrap step.
Common bad pattern:
public sealed class SomeStartupStep
{
private readonly ILogger _logger = LogManager.CreateLogger("Startup");
}
Safer pattern:
public sealed class SomeStartupStep
{
private ILogger _logger;
private ILogger Logger => _logger ??= LogManager.CreateLogger("Startup");
}
2. Category Rules
- Use stable domain categories such as
Gameplay,Networking,Economy,UI,Stability. - Use subcategories only when they help with filtering or ownership.
- Do not create ad-hoc category names from dynamic data.
Why:
- categories drive filtering and override rules in
LogTargetConfiguration - loggers are cached by category and subcategory key
3. Logger API Usage Rules
- Prefer direct
ILoggermethods such asLogError(...),LogWarning(...),LogInfo(...),LogDebug(...), andLogException(...). - If a call site already has a variable
LogLevel, preferILogger.LogFormat(logLevel, message, attributes)over a project-local wrapper that re-dispatches the level manually. - Do not add convenience wrappers that collapse multiple levels into fewer outputs or silently remap
Info,Exception, or custom flows toDebug. - Keep the call site explicit enough that a reviewer can see the intended severity without opening another helper.
- If a call site already has a real
Exception, pass the realExceptionobject into the logger. - Prefer
logger.LogException(exception)orlogger.LogError("context message", exception)over logging onlyexception.Messageor onlyexception.StackTrace. - Do not downgrade a structured exception into a plain string too early, because that loses type, inner-exception chain, and tooling support in targets.
- Do not unwrap
AggregateExceptionto only oneInnerExceptionunless that is a deliberate product decision and the lost context is acceptable.
Bad pattern:
private static void Log(ILogger logger, string message, LogLevel logLevel, LogAttributes attributes)
{
if (logger == null || string.IsNullOrEmpty(message))
{
return;
}
switch (logLevel)
{
case LogLevel.Error:
logger.LogError(message, attributes);
break;
case LogLevel.Warning:
logger.LogWarning(message, attributes);
break;
default:
logger.LogDebug(message, attributes);
break;
}
}
Why this is bad:
- it hides the real logger API behind a local adapter
- it silently remaps
InfotoDebug - it makes
Exceptionhandling unclear - it makes audit and grep of real severity usage harder
Preferred patterns:
logger.LogWarning("config is missing", attributes);
logger.LogError("startup failed", attributes);
logger.LogInfo("bootstrap completed", attributes);
logger.LogFormat(logLevel, message, attributes);
3a. Exception Handling Rules
Use these rules for app code around the logger and for custom integrations that extend the package.
- Treat logger delivery paths as
never-throwin production runtime. - A broken log target should fail in isolation and should not take down gameplay, app startup, app shutdown, or other healthy targets.
- A custom target, custom log source, or async helper that catches an exception should log the original exception object when possible, not only its string fields.
- If a logger callback, scheduler callback, or third-party SDK callback can throw, catch that exception inside the logging path and degrade to fallback logging instead of letting it escape into the app flow.
- If a target becomes unhealthy at runtime, mute or disable that target and continue routing logs to the remaining healthy targets.
- Keep exception logging explicit in code review. A reviewer should be able to see where the original exception object is preserved and where failure isolation is owned.
Bad pattern:
catch (Exception ex)
{
logger.LogError($"Request failed: {ex.Message}");
}
Why this is bad:
- it loses the exception type
- it loses the inner-exception chain
- it weakens downstream formatting and target-specific exception handling
Preferred pattern:
catch (Exception ex)
{
logger.LogError("Request failed.", ex);
}
If the app must rethrow after logging:
- use
throw;when rethrowing the same caught exception from the samecatch - use
ExceptionDispatchInfo.Capture(exception).Throw();when rethrowing later or rethrowing an extracted inner exception - do not use
throw ex; - do not use
throw ex.InnerException;
If your integration uses reflection invoke:
- prefer the
Invoke(...)overload that allowsBindingFlags.DoNotWrapExceptionswhen that is available in your target runtime - otherwise assume reflection may wrap the real failure in
TargetInvocationExceptionand preserve the original exception object when logging or rethrowing
For fire-and-forget task usage inside integration code:
- prefer package-owned safe helpers such as
FireAndForget()orFireAndLogWhenExceptions(...)over rawasync void - if you add your own task helpers, keep their logging path
never-throw - cancellation should stay a normal non-crash path and should not be promoted into a fatal logger failure
4. Log Target Configuration Rules
Every target inherits a common configuration surface:
MutedMinLogLevelOverrideCategoriesBatchLogsDebugModeStackTracesIsThreadSafeDispatchingLogsToMainThread
Recommended defaults:
- keep
MinLogLevelatWarningor higher for expensive remote sinks - keep stack traces enabled mainly for
ErrorandException - use
OverrideCategoriesfor hot categories that need lower or higher verbosity - mark thread-safe targets honestly
- do not enable
DispatchingLogsToMainThreadfor thread-safe targets unless you have a specific reason
Why the last rule matters:
- the logger already warns when a target is marked
IsThreadSafeand main-thread dispatch is still enabled - dispatching adds queueing and main-thread work, so it should be intentional
5. Batching and Main-Thread Dispatch
Use BatchLogs when:
- the target is remote
- writes are expensive
- bursty logging would otherwise create too many individual operations
Use DispatchingLogsToMainThread when:
- the target must execute on the Unity main thread
- background-thread logs must still reach that target safely
Recommended combinations:
- thread-safe remote sink:
BatchLogs.Enabled = trueDispatchingLogsToMainThread.Enabled = false
- non-thread-safe Unity-facing sink:
DispatchingLogsToMainThread.Enabled = true- batching optional depending on how expensive sink calls are
Important nuance:
Criticallog importance forces immediate drain and send- regular and
NiceToHavelogs can be batched
6. Debug Mode Strategy
DebugModeConfiguration supports:
EnabledMinLogLevelIDs- per-category overrides
Recommended use:
- keep production defaults conservative
- enable high-verbosity debug mode only for approved device or player identifiers
- use
SetDebugMode(...)to amplify diagnostics without turning the whole fleet noisy
Important nuance:
- initialization already tries to enable debug mode for the provided
debugId, or falls back toSystemInfo.deviceUniqueIdentifier
Target-Specific Guidance
UnityEditorConsoleLogTarget
Good for:
- local development
- editor diagnosis
- surfacing category-prefixed messages and exceptions into the standard Unity Console
Do not treat it as:
- a production sink
- a substitute for runtime or device observability
AndroidSystemLogTarget
Good for:
- native Android Logcat visibility
- device-side diagnosis when Unity Console is not enough
Best practices:
- keep payloads concise
- do not rely on this target alone for long-term retention
- still validate on physical Android devices before making platform confidence claims
AppleSystemLogTarget
Good for:
- Apple unified logging visibility on supported Apple targets
Best practices:
- pass stable subsystem and main category values
- still validate on physical Apple runtime before making production claims about native observability
OpenSearchLogTarget
Treat this example target as production-sensitive if you use it in a real backend pipeline.
Current config surface:
OpenSearchHostUrlOpenSearchSingleLogMethodIndexPrefixApiKey
Useful current behavior:
- config merge only overwrites those fields when the incoming value is non-empty
- this is compatible with partial remote-config refresh patterns
- DTO payload includes platform, OS, device model, UUID, log level, category, message, stack trace, timestamp, tags, attributes, and debug mode state
Operational cautions:
- timeout policy is not yet part of the public config surface
- remote delivery success and failure behavior should be validated per environment
- use batching and conservative verbosity for remote sinks
SafeThirdPartyLogTarget
Use this base class when a third-party SDK can fail at runtime and should not be allowed to take the logger down with it.
Best practices:
- prefer guarded integration over direct unsafe target calls
- isolate third-party initialization failures
- if a third-party sink throws at runtime, mute or disable that sink and keep the rest of the logger alive
- treat fault isolation as part of release validation, not just implementation detail
FileBackgroundAsyncWriter
Use it as a utility for background file writes, not as a reason to ignore shutdown behavior.
Best practices:
- keep disposal asynchronous internally
- avoid sync-waiting on teardown paths that can capture Unity
SynchronizationContext - validate scene changes and shutdown while logs are still in flight
Log Source Recommendations
The package can intake logs from several sources, including:
UnityDebugLogSourceUnityApplicationLogSourceUnityApplicationLogSourceThreaded- unobserved
Taskexceptions - unobserved
UniTaskexceptions - current-domain unhandled exceptions
- optional
System.Diagnosticssources
Recommended approach:
- start with the smallest source set that gives you the evidence you need
- decide explicitly whether you want threaded application log capture
- enable extra exception-oriented sources in production when you need crash-path visibility
- validate source behavior with runtime tests, not only editor assumptions
- for unobserved task paths, prefer preserving the full exception object or flattened aggregate instead of collapsing the signal to one inner exception
StabilityHub Guidance
Use StabilityHub when previous-session crash or stability recovery signals matter to the product.
Best practices:
- initialize
StabilityHubServiceafter logger bootstrap - treat missing monitoring config as a valid disabled state
- keep stability logging on its own category so it is easy to filter and route
Example package-owned flow exists in:
Runtime/Examples/GameLoggerEntryPoint.cs
Performance Guidance
If the goal is low allocations and low impact on Unity main-thread smoothness:
- initialize once and early
- keep per-frame hot-path logs rare
- avoid stack traces on
InfoandDebugunless actively diagnosing - use batching for remote sinks
- avoid unnecessary main-thread dispatch
- do not turn on broad debug mode for the whole population
- validate editor performance and playmode performance separately
- keep consumer validation and device proof in the release process
Recommended validation stack:
- editor tests for deterministic logic and config behavior
- playmode tests for runtime dispatch, batching, and frame behavior
- consumer validation in a real integration workspace
- physical device proof for native-platform confidence and device-facing perf claims
Simple Rollout Checklist
- logger initializes from main thread
- at least one target is configured and resolved
- categories are stable
- stack traces are mostly limited to
ErrorandException - no obviously expensive hot-path logging
- logs are visible in editor or on target device
Pro Rollout Checklist
- target mix is intentional
- thread-safety flags are reviewed
- batch and dispatch settings are reviewed per target
- debug mode IDs are configured intentionally
- remote target config can be updated safely
- consumer validation is green
- performance evidence exists
- native/device proof exists for platform-native targets
What Another AI Should Check
If you ask another AI to review a project that already integrated TheBestLogger, make sure it checks these bootstrap-specific risks explicitly:
LogManager.Initialize(...)order relative to the real app bootstrap- any
LogManager.CreateLogger(...)call that can run before initialization - loggers created from constructors
- loggers created from static fields or static properties
- bootstrap steps that log during their own construction
- fallback-logger warnings that indicate early logger access instead of a properly initialized runtime
- whether logger creation is lazy or injected after bootstrap