Unlog is a header only library made for Unreal Engine that aims to make logging more effortless and feature-full. Some of the features are still experimental are prone to changing. Please leave some feedback on our Issues page.
Feature | Status |
---|---|
Define categories at any scope with just one line of code | ✅ |
Automatically infer category based on scoped categories | ✅ |
Easily write to other output targets (e.g in-game viewport, Message Log) | ✅ |
Modern C++ logging syntax with type safety | ✅ |
Retro-compatible support for UE_LOG macro syntax by using UN_LOG | ✅ |
Create your own logging targets | ✅ |
Remove debug strings from the binary when on shipping builds | ✅ |
Static polymorphism makes sure compiler does most of the work | ✅ |
Possibility of a few bugs | 🐛 |
Feature | Status |
---|---|
UNLOG macro function options | 🔨 |
Unlog makes it easier to declare log categories anywhere in code while still respecting the scope they are declared on.
#include <Unlog/Unlog.h>
virtual void BeginPlay() override
{
// Declare log categories at any scope
UNLOG_CATEGORY( MeaningOfLifeCategory );
// Option A: Using function for maximum type safety
Unlog::Log< MeaningOfLifeCategory >( "Evaluating the meaning of life." );
// Option B (Experimental): Log using the custom macro options
UNLOG( MeaningOfLifeCategory, Log )( "Evaluating the meaning of life" );
// Option C (Retro-compatible): Use the UE_LOG syntax
UN_LOG( MeaningOfLifeCategory, Log, "Evaluating the meaning of life ")
}
You can also remove the category altogether so Unlog can implicitly pick the category to use.
// Automatically picks the category to use when none is specified
Unlog::Log( "Starting calculation" );
// Output:
//> LogGeneral: Starting calculation
// Same but using the macro option
UNLOG( Log )( "Starting calculation" );
// Output:
//> LogGeneral: Starting calculation
Note
When omitting the category, it will first try to pick any scoped category that is part of the stack or the default category defined on the setup
Scoped categories are a powerful way to apply a log category to an entire scope. Meaning all logs without explicit categories will automatically be attributed to the in-scope category.
void GoHomeRoutine()
{
UNLOG_CATEGORY_SCOPED( GoHomeRoutine );
Unlog::Log( "Character '{0}' starting routine", CharacterName );
...
Unlog::Error( "Character '{0}' unable to GoHome due to colliding wall", CharacterName );
}
void Execute()
{
UNLOG_CATEGORY_SCOPED( RoutineEvaluation );
Unlog::Log("Starting routine evaluation");
if( ShouldGoHome() )
{
GoHomeRoutine();
}
Unlog::Log("Successfully finished routine evaluation");
}
// Output:
// > RoutineEvaluation: Starting routine evaluation
// > GoHomeRoutine: Character 'Gandalf' starting routine
// > GoHomeRoutine: Error: Character 'Gandalf' unable to GoHome due to colliding wall
// > RoutineEvaluation: Successfully finished routine evaluation
Unlog has been tested to work from UE 4.26 to UE 5.3. Since the library targets C++14 features, it should theoretically support even older engine versions. It has also been tested on all the major operating systems: Windows, Linux and MacOS with their respective toolchains.
UE_LOG is the tried and tested method of logging in Unreal which is extensively used across thousands of games and projects. It's solid. It also means it's less accepting of changes, some of which could be beneficial to iteration times and usability. Since this project is an attempt to find ways to enhance logging in Unreal, we're willing to sacrifice some of this stability for more features.
To use Unlog clone this repository into the Source folder of your project or plugin. After cloning the folder structure should resemble <ProjectName>/Source/Unlog/Unlog.h
.
Opening Unlog.h
lets you configure the default logger (named Unlog
) with the settings more appropriate for your case.
#pragma once
#include <UnlogImplementation.h>
// Example A: Just a simple logger
using Unlog = TUnlog<>;
You can also customize the logger with the templated builder pattern:
#pragma once
#include <UnlogImplementation.h>
// Example B: Logger with custom output targets and a different default category
UNLOG_CATEGORY( MyLogCategory );
using Unlog = TUnlog<>::WithTargets< Target::UELog, Target::Viewport >
::WithDefaultCategory< MyLogCategory >;
Important
For now it's required to have defined a logger named Unlog
when using the UNLOG macro.
You're also free use this area to declare global categories and other loggers:
UNLOG_CATEGORY( GlobalCategory );
using ScreenLogger = TUnlog<>::WithTargets< Target::Viewport >
::WithCategory< GlobalCategory >;
Note
Since Unlog is a header only solution, it doesn't need to incorporated into a specific module to export symbols.
Not taking in pull requests for now. Sorry!
// Using functions
Unlog::Log( "Just a log" );
Unlog::Warn( "A warning!" );
Unlog::Error( "An error!!" );
Unlog::Verbose( "A log of dubious value" );
// Using macros
UNLOG( Log )( "Just a log" );
UNLOG( Warning )( "A warning!" );
UNLOG( Error )( "An error!!" );
UNLOG( Verbose )( "A Log of dubious value" );
// Passing a boolean condition as first argument so it only happens when the condition is met
Unlog::Warn( !bIsActive, "Trying to execute operation when component isn't active" );
// Passing a lambda (returning a bool) makes sure the expression is only evaluated if logging is enabled
Unlog::Warn( []{ return OnlyCalledWhenLoggingIsActive(); }, "Condition not met" );
// Macro variant automatically compiles out condition when logging is disabled.
UNCLOG( !bIsActive, Category, Warning )( "Trying to execute operation when component isn't active" );
By default Unlog uses the numbered format approach to incorporate values into the message string.
Unlog::Log( "Object '{0}' created at {1} with value {2}",
GetNameSafe(MaterialExpression), FDateTime::Now().ToString(), 42 );
//Or
UNLOG(Log)( "Object '{0}' created at {1} with value {2}",
GetNameSafe(MaterialExpression), FDateTime::Now().ToString(), 42 );
// Output:
// > Object 'MaterialExpression_0' created at 2023.08.23-19.58.49 with value 42
Or you can always use the old trustable printf by using the "f" suffix function variants:
Unlog::Logf( "Object %s created at %s with value %d",
*GetNameSafe(MaterialExpression), *FDateTime::Now().ToString(), 42 );
//Or
UNLOGF(Log)( "Object %s created at %s with value %d",
*GetNameSafe(MaterialExpression), *FDateTime::Now().ToString(), 42 );
// Output:
// > Object 'MaterialExpression_0' created at 2023.08.23-19.58.49 with value 42
At any point you can create a custom logger to output to other targets:
using MyLogger = TUnlog<>
::WithCategory< MyCategory >
::WithTargets< Target::TViewport< 10, FColor::Red > >;
MyLogger::Error("Failed to spawn actor!");
// Macro function also accepts a custom logger as the first parameter!
UNLOG( MyLogger, Error )( "Failed to spawn actor!" )
Unlog bypasses the need to wrap your text in the TEXT() macro by automatically converting char
string literals into the TCHAR
set.
// Logging in runes like it is 500 A.D.
Unlog::Error("ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ");
// Output:
// > LogGeneral: ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ
// Output without automatic conversion
// > LogGeneral: ᚻᛖ ᚳᚹᚫᚦ ᚦᚫ� ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹ�ᚦ ᚦᚪ ᚹᛖᛥᚫ
Note
It's important to note that this conversion results in one extra string allocation every time the log expression is hit. If you wish to bypass this allocation completely you should still wrap text with TEXT() macro.
The UNLOG macro automatically wraps the format text with the TEXT() macro so you won't have to do it. Doing so will result in an compilation error complaining about 'LL': undeclared identifier
.
Disabling Unlog will automatically cull out string literals used in logs from the compilation. This helps making the program smaller (even if by just a fraction) and stops potentially sensitive or redundant information to reach the user.
Using the UNLOG macro 100% guarantees the strings are culled. Using the logging functions will almost always result on culling when running at least the minimum level of compiler optimizations (tested on MSVC locally and on gcc using godbolt.com), but it can depend if other functions are being called as part of the logging function arguments.
Unlog is totally free for non-commercial purposes and most commercial projects — no licenses need to be purchased for projects with a budget (or revenue) below $250K.
This basically means 99% percent of users won't need to pay anything to use it. Instead we believe that if larger developers ever start to extract value out of this library, at their scale, then they're probably at a good position to contribute directly towards its development by acquiring a moderately priced commercial license (which also provides other boons). We want to make this process more transparent in the future, but our focus is on growing the library first.
Please reach out to us if you've got any queries: unlog@guganana.com
Lines 1 to 12 in 122af35
Shout-out to https://github.com/ChristianPanov/lwlog which has been a good source of inspiration for some of the code design patterns.