Browse Source
Now `LoggerAPI`, instead of simply loggin messages by itself, uses logger objects (deriving from `Logger` class) to output log messages for it (previous way of logging by `Log()` method is available by `ConsoleLogger`). Multiple loggers can be configured per each log level, which can be done via config.pull/8/head
Anton Tarasenko
4 years ago
20 changed files with 1152 additions and 278 deletions
@ -0,0 +1,41 @@
|
||||
/** |
||||
* Simple logger class that uses `Log` method to print all of the |
||||
* log messages. Supports all of the default `acediaStamp`, `timeStamp` and |
||||
* `levelStamp` settings |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class ConsoleLogger extends Logger |
||||
perObjectConfig |
||||
config(AcediaSystem) |
||||
dependson(LoggerAPI); |
||||
|
||||
public function Write(Text message, LoggerAPI.LogLevel messageLevel) |
||||
{ |
||||
local MutableText builder; |
||||
if (message != none) |
||||
{ |
||||
builder = GetPrefix(messageLevel); |
||||
builder.Append(message); |
||||
Log(builder.ToPlainString()); |
||||
builder.FreeSelf(); |
||||
} |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
} |
@ -0,0 +1,355 @@
|
||||
/** |
||||
* Object of this class is meant to represent a single log message that |
||||
* can have parameters, specified by "%<number>" definitions |
||||
* (e.g. "Thing %1 conflicts with thing %2, so we will have to remove %3"). |
||||
* Log message only has to prepare (break into parts) provided human-readable |
||||
* string once and then will be able to quickly perform argument insertion |
||||
* (for which several convenient `Arg*()` methods are provided). |
||||
* The supposed way to use `LogMessage` is is in conjunction with |
||||
* `LoggerAPI`'s `Auto()` method that takes `Definition` with pre-filled |
||||
* message (`m`) and type (`t`), then: |
||||
* 1. (first time only) Generates a new `LogMessage` from them; |
||||
* 2. Returns `LogMessage` object that, whos arguments are supposed to be |
||||
* filled with `Arg*()` methods; |
||||
* 3. When the appropriate amount of `Arg*()` calls (by the number of |
||||
* specified "%<number>" tags) was made - logs resulting message. |
||||
* (4). If message takes no arguments - no `Arg*()` calls are necessary; |
||||
* (5). If new `Auto()` call is made before previous message was provided |
||||
* enough arguments - error will be logged and previous message |
||||
* will be discarded (along with it's arguments). |
||||
* For more information about using it - refer to the documentation. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class LogMessage extends AcediaObject; |
||||
|
||||
// Flag to prevent `Initialize()` being called several times in a row |
||||
var private bool isInitialized; |
||||
// With what level was this message initialized? |
||||
var private LoggerAPI.LogLevel myLevel; |
||||
|
||||
/** |
||||
* We essentially break specified log string into parts |
||||
* (replacing empty parts with `none`) like this: |
||||
* "This %1 is %3, not %2" => |
||||
* * "This " |
||||
* * " is " |
||||
* * ", not " |
||||
* * `none` |
||||
* Arguments always fit between these parts, so if there is `N` parts, |
||||
* there will be `N - 1` arguments. We consider that we are done filling |
||||
* arguments when amount of `Arg*()` calls reaches that number. |
||||
* |
||||
* For future localization purposes we do not assume that arguments are |
||||
* specified from left to right in order: in our example, |
||||
* if we make following calls: `.Arg("one").Arg("caged").Arg("free")`, |
||||
* we will get the string: "This one is free, not caged". |
||||
* To remember the order we keep a special array, in our case it would be |
||||
* [1, 3, 2], except normalized to start from zero: [0, 2, 1]. |
||||
*/ |
||||
// Parts of the initial message, broken by "%<number> tags |
||||
var private array<Text> logParts; |
||||
// Defines order of arguments: |
||||
// `i` -> argument at what number to use at insertion place `i`? |
||||
// (`i` starting from zero instead of `1`). |
||||
var private array<int> normalizedArguments; |
||||
|
||||
// Only default value is used for this variable: it remembers what |
||||
// `LogMessage` currently stores "garbage": temporary `Text` object to create |
||||
// a log message. Making an `Arg*()` call on any other `LogMessage` will cause |
||||
// progress of `default.dirtyLogMessage` to be reset, thus enforcing that only |
||||
// one `LogMessage` can be in the process of filling itself with arguments at |
||||
// a time and, therefore, only one can be "dirty": contain temporary |
||||
// `Text` objects. |
||||
// This way using `LogMessage` would not lead to accumulating large |
||||
// amounts of trash objects, since only one of them can "make a mess". |
||||
var private LogMessage dirtyLogMessage; |
||||
// Arguments, collected so far by the `Arg*()` calls |
||||
var private array<Text> collectedArguments; |
||||
|
||||
protected function Finalizer() |
||||
{ |
||||
isInitialized = false; |
||||
_.memory.FreeMany(logParts); |
||||
_.memory.FreeMany(collectedArguments); |
||||
logParts.length = 0; |
||||
collectedArguments.length = 0; |
||||
normalizedArguments.length = 0; |
||||
} |
||||
|
||||
/** |
||||
* Initialize new `LogMessage` object by a given definition. |
||||
* Can only be done once. |
||||
* |
||||
* Correct functionality is guaranteed when arguments start from either |
||||
* `0` or `1` and then increase in order, without gaps or repetitions. |
||||
* `Initialize()` will attempt to correctly initialize `LogMessage` in case |
||||
* these rules are broken, by making assumptions about user's intentions, |
||||
* but behavior in that case should be considered undefined. |
||||
* |
||||
* @param logMessageDefinition Definition to take message parameter from. |
||||
*/ |
||||
public final function Initialize(LoggerAPI.Definition logMessageDefinition) |
||||
{ |
||||
local int nextArgument; |
||||
local Parser parser; |
||||
local MutableText nextLogPart, nextChunk; |
||||
local Text.Character percentCharacter; |
||||
local array<int> parsedArguments; |
||||
if (isInitialized) { |
||||
return; |
||||
} |
||||
isInitialized = true; |
||||
myLevel = logMessageDefinition.l; |
||||
percentCharacter = _.text.GetCharacter("%"); |
||||
parser = _.text.ParseString(logMessageDefinition.m); |
||||
nextLogPart = _.text.Empty(); |
||||
// General idea is simply to repeat: parse until "%" -> parse "%<number>" |
||||
while (!parser.HasFinished()) |
||||
{ |
||||
parser.MUntil(nextChunk, percentCharacter).Confirm(); |
||||
nextLogPart.Append(nextChunk); |
||||
// If we cannot parse "%" after `MUntil(nextChunk, percentCharacter)`, |
||||
// then we have parsed everything |
||||
if (!parser.Match(P("%")).Confirm()) { |
||||
break; |
||||
} |
||||
// We need to check whether it i really "%<number>" tag and not |
||||
// just a "%" symbol without number |
||||
if (parser.MInteger(nextArgument).Confirm()) |
||||
{ |
||||
parsedArguments[parsedArguments.length] = nextArgument; |
||||
logParts[logParts.length] = nextLogPart.Copy(); |
||||
nextLogPart.Clear(); |
||||
} |
||||
else |
||||
{ |
||||
// If it is just a symbol - simply add it |
||||
nextLogPart.AppendCharacter(percentCharacter); |
||||
parser.R(); |
||||
} |
||||
} |
||||
logParts[logParts.length] = nextLogPart.Copy(); |
||||
parser.FreeSelf(); |
||||
nextLogPart.FreeSelf(); |
||||
CleanupEmptyLogParts(); |
||||
NormalizeArguments(parsedArguments); |
||||
} |
||||
|
||||
// Since `none` and empty `Text` will be treated the same way by the `Append()` |
||||
// operation, we do not need to keep empty `Text` objects and can simply |
||||
// replace them with `none`s |
||||
private final function CleanupEmptyLogParts() |
||||
{ |
||||
local int i; |
||||
for (i = 0; i < logParts.length; i += 1) |
||||
{ |
||||
if (logParts[i].IsEmpty()) |
||||
{ |
||||
logParts[i].FreeSelf(); |
||||
logParts[i] = none; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Normalize enumeration by replacing them with natural numbers sequence: |
||||
// [0, 1, 2, ...] in the same order: |
||||
// [2, 6, 3] -> [0, 2, 1] |
||||
// [-2, 0, 4, -7] -> [1, 2, 3, 0] |
||||
// [1, 1, 2, 1] -> [0, 1, 3, 2] |
||||
private final function NormalizeArguments(array<int> argumentsOrder) |
||||
{ |
||||
local int i; |
||||
local int nextArgument; |
||||
local int lowestArgument, lowestArgumentIndex; |
||||
normalizedArguments.length = 0; |
||||
normalizedArguments.length = argumentsOrder.length; |
||||
while (nextArgument < normalizedArguments.length) |
||||
{ |
||||
// Find next minimal index and record next natural number |
||||
// (`nextArgument`) into it |
||||
for (i = 0; i < argumentsOrder.length; i += 1) |
||||
{ |
||||
if (argumentsOrder[i] < lowestArgument || i == 0) |
||||
{ |
||||
lowestArgumentIndex = i; |
||||
lowestArgument = argumentsOrder[i]; |
||||
} |
||||
} |
||||
argumentsOrder[lowestArgumentIndex] = MaxInt; |
||||
normalizedArguments[lowestArgumentIndex] = nextArgument; |
||||
nextArgument += 1; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Fills next argument in caller `LogMessage` with given `Text` argument. |
||||
* |
||||
* When used on `LogMessage` returned from `LoggerAPI.Auto()` call - filling |
||||
* all arguments leads to message being logged. |
||||
* |
||||
* @param argument This argument will be managed by the `LogMessage`: |
||||
* you should not deallocate it by hand or rely on passed `Text` to not |
||||
* be deallocated. This also means that you should not pass `Text` objects |
||||
* returned by `P()`, `C()` or `F()` calls. |
||||
* @return Caller `LogMessage` to allow for method chaining. |
||||
*/ |
||||
public final function LogMessage Arg(Text argument) |
||||
{ |
||||
local Text assembledMessage; |
||||
if (IsArgumentListFull()) { |
||||
return self; |
||||
} |
||||
// Do we need to clean old `LogMessage` from it's arguments first? |
||||
if (default.dirtyLogMessage != none && default.dirtyLogMessage != self) { |
||||
default.dirtyLogMessage.Reset(); |
||||
} |
||||
default.dirtyLogMessage = self; // `self` is dirty with arguments now |
||||
collectedArguments[collectedArguments.length] = argument; |
||||
if (IsArgumentListFull()) |
||||
{ |
||||
// Last argument - have to log what we have collected |
||||
assembledMessage = Collect(); |
||||
_.logger.LogAtLevel(assembledMessage, myLevel); |
||||
assembledMessage.FreeSelf(); |
||||
return self; |
||||
} |
||||
return self; |
||||
} |
||||
|
||||
// Check whether we have enough arguments to completely make log message: |
||||
// each argument goes in between two log parts, so there is |
||||
// `logParts.length - 1` arguments total. |
||||
private final function bool IsArgumentListFull() |
||||
{ |
||||
return collectedArguments.length >= logParts.length - 1; |
||||
} |
||||
|
||||
/** |
||||
* Fills next argument in caller `LogMessage` with given `int` argument. |
||||
* |
||||
* When used on `LogMessage` returned from `LoggerAPI.Auto()` call - filling |
||||
* all arguments leads to message being logged. |
||||
* |
||||
* @param argument This value will be converted into `Text` and pasted |
||||
* into the log message. |
||||
* @return Caller `LogMessage` to allow for method chaining. |
||||
*/ |
||||
public final function LogMessage ArgInt(int argument) |
||||
{ |
||||
return Arg(_.text.FromInt(argument)); |
||||
} |
||||
|
||||
/** |
||||
* Fills next argument in caller `LogMessage` with given `float` argument. |
||||
* |
||||
* When used on `LogMessage` returned from `LoggerAPI.Auto()` call - filling |
||||
* all arguments leads to message being logged. |
||||
* |
||||
* @param argument This value will be converted into `Text` and pasted |
||||
* into the log message. |
||||
* @return Caller `LogMessage` to allow for method chaining. |
||||
*/ |
||||
public final function LogMessage ArgFloat(float argument) |
||||
{ |
||||
return Arg(_.text.FromFloat(argument)); |
||||
} |
||||
|
||||
/** |
||||
* Fills next argument in caller `LogMessage` with given `bool` argument. |
||||
* |
||||
* When used on `LogMessage` returned from `LoggerAPI.Auto()` call - filling |
||||
* all arguments leads to message being logged. |
||||
* |
||||
* @param argument This value will be converted into `Text` and pasted |
||||
* into the log message. |
||||
* @return Caller `LogMessage` to allow for method chaining. |
||||
*/ |
||||
public final function LogMessage ArgBool(bool argument) |
||||
{ |
||||
return Arg(_.text.FromBool(argument)); |
||||
} |
||||
|
||||
/** |
||||
* Fills next argument in caller `LogMessage` with given `class<Object>` |
||||
* argument. |
||||
* |
||||
* When used on `LogMessage` returned from `LoggerAPI.Auto()` call - filling |
||||
* all arguments leads to message being logged. |
||||
* |
||||
* @param argument This value will be converted into `Text` and pasted |
||||
* into the log message. |
||||
* @return Caller `LogMessage` to allow for method chaining. |
||||
*/ |
||||
public final function LogMessage ArgClass(class<Object> argument) |
||||
{ |
||||
return Arg(_.text.FromClass(argument)); |
||||
} |
||||
|
||||
/** |
||||
* Resets current progress of filling caller `LogMessage` with arguments, |
||||
* deallocating already passed ones. |
||||
* |
||||
* @return Caller `LogMessage` to allow for method chaining. |
||||
*/ |
||||
public final function LogMessage Reset() |
||||
{ |
||||
_.memory.FreeMany(collectedArguments); |
||||
collectedArguments.length = 0; |
||||
return self; |
||||
} |
||||
|
||||
/** |
||||
* Returns `LogMessage`, assembled with it's arguments into the `Text`. |
||||
* |
||||
* If some arguments were not yet filled - they will treated as empty `Text` |
||||
* values. |
||||
* |
||||
* This result will be reset if `Reset()` method is called or another |
||||
* `LogMessage` starts filling itself with arguments. |
||||
* |
||||
* @return Caller `LogMessage`, assembled with it's arguments into the `Text`. |
||||
*/ |
||||
public final function Text Collect() |
||||
{ |
||||
local int i, argumentIndex; |
||||
local Text result; |
||||
local Text nextArgument; |
||||
local MutableText builder; |
||||
if (logParts.length == 0) { |
||||
return P("").Copy(); |
||||
} |
||||
builder = _.text.Empty(); |
||||
for (i = 0; i < logParts.length - 1; i += 1) |
||||
{ |
||||
nextArgument = none; |
||||
// Since arguments might not be specified in order - |
||||
argumentIndex = normalizedArguments[i]; |
||||
if (argumentIndex < collectedArguments.length) { |
||||
nextArgument = collectedArguments[argumentIndex]; |
||||
} |
||||
builder.Append(logParts[i]).Append(nextArgument); |
||||
} |
||||
builder.Append(logParts[logParts.length - 1]); |
||||
result = builder.Copy(); |
||||
builder.FreeSelf(); |
||||
return result; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
} |
@ -0,0 +1,151 @@
|
||||
/** |
||||
* Base class for implementing "loggers" - objects that actually write log |
||||
* messages somewhere. To use it - simply implement `Write()` method, |
||||
* preferably making use of `GetPrefix()` method. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class Logger extends AcediaObject |
||||
perObjectConfig |
||||
config(AcediaSystem) |
||||
dependson(LoggerAPI) |
||||
abstract; |
||||
|
||||
// Named loggers are stored here to avoid recreating them |
||||
var private AssociativeArray loadedLoggers; |
||||
|
||||
// Should `Logger` display prefix indicating it's a log message from Acedia? |
||||
var protected config bool acediaStamp; |
||||
// Should `Logger` display time stamp prefix in front of log messages? |
||||
var protected config bool timeStamp; |
||||
// Should `Logger` display information about what level message was logged? |
||||
var protected config bool levelStamp; |
||||
|
||||
var protected const int TDEBUG, TINFO, TWARNING, TERROR, TFATAL, TTIME, TACEDIA; |
||||
var protected const int TSPACE; |
||||
|
||||
/** |
||||
* Method for creating named `Logger`s that can have their settings prepared |
||||
* in the config file. Only one `Logger` is made for every |
||||
* unique (case insensitive) `loggerName`. |
||||
* |
||||
* @param loggerName Name of the logger instance to return. Consequent calls |
||||
* with the same `loggerName` value will return the same `Logger`, |
||||
* unless it is deallocated. |
||||
* @return Logger with object name `loggerName`. |
||||
*/ |
||||
public final static function Logger GetLogger(Text loggerName) |
||||
{ |
||||
local Logger loggerInstance; |
||||
local Text loggerKey; |
||||
if (default.loadedLoggers == none) |
||||
{ |
||||
// TODO: do this in static constructor |
||||
default.loadedLoggers = __().collections.EmptyAssociativeArray(); |
||||
} |
||||
if (loggerName == none) { |
||||
return none; |
||||
} |
||||
loggerKey = loggerName.LowerCopy(); |
||||
loggerInstance = Logger(default.loadedLoggers.GetItem(loggerKey)); |
||||
if (loggerInstance == none) |
||||
{ |
||||
// TODO: important to redo this via `MemoryAPI` to call constructors |
||||
loggerInstance = new(none, loggerName.ToPlainString()) default.class; |
||||
loggerInstance._constructor(); |
||||
default.loadedLoggers.SetItem(loggerKey, loggerInstance); |
||||
} |
||||
loggerKey.FreeSelf(); |
||||
return loggerInstance; |
||||
} |
||||
|
||||
/** |
||||
* Auxiliary method for generating log message prefix based on `acediaStamp`, |
||||
* `timeStamp` and `levelStamp` flags according to their description. |
||||
* Method does not provide any guarantees on how exactly. |
||||
* |
||||
* @param messageLevel Message level for which to generate prefix. |
||||
* @return Text (mutable) representation of generated prefix. |
||||
*/ |
||||
protected function MutableText GetPrefix(LoggerAPI.LogLevel messageLevel) |
||||
{ |
||||
local MutableText builder; |
||||
builder = _.text.Empty(); |
||||
if (acediaStamp) { |
||||
builder.Append(T(TACEDIA)); |
||||
} |
||||
if (timeStamp) { |
||||
builder.Append(T(TTIME)); |
||||
} |
||||
// Make output prettier by adding a space after the "[...]" prefixes |
||||
if (!levelStamp && (acediaStamp || timeStamp)) { |
||||
builder.Append(T(TSPACE)); |
||||
} |
||||
if (!levelStamp) { |
||||
return builder; |
||||
} |
||||
switch (messageLevel) |
||||
{ |
||||
case LOG_Debug: |
||||
builder.Append(T(TDEBUG)); |
||||
break; |
||||
case LOG_Info: |
||||
builder.Append(T(TINFO)); |
||||
break; |
||||
case LOG_Warning: |
||||
builder.Append(T(TWARNING)); |
||||
break; |
||||
case LOG_Error: |
||||
builder.Append(T(TERROR)); |
||||
break; |
||||
case LOG_Fatal: |
||||
builder.Append(T(TFATAL)); |
||||
break; |
||||
default: |
||||
} |
||||
return builder; |
||||
} |
||||
|
||||
/** |
||||
* Method that must perform an actual work of outputting message `message` |
||||
* at level `messageLevel`. |
||||
* |
||||
* @param message Message to output. |
||||
* @param messageLevel Level, at which message must be output. |
||||
*/ |
||||
public function Write(Text message, LoggerAPI.LogLevel messageLevel){} |
||||
|
||||
defaultproperties |
||||
{ |
||||
// Parts of the prefix for our log messages, redirected into kf log file. |
||||
TDEBUG = 0 |
||||
stringConstants(0) = "[Debug] " |
||||
TINFO = 1 |
||||
stringConstants(1) = "[Info] " |
||||
TWARNING = 2 |
||||
stringConstants(2) = "[Warning] " |
||||
TERROR = 3 |
||||
stringConstants(3) = "[Error] " |
||||
TFATAL = 4 |
||||
stringConstants(4) = "[Fatal] " |
||||
TTIME = 5 |
||||
stringConstants(5) = "[hh:mm:ss]" |
||||
TACEDIA = 6 |
||||
stringConstants(6) = "[Acedia]" |
||||
TSPACE = 7 |
||||
stringConstants(7) = " " |
||||
} |
@ -1,166 +0,0 @@
|
||||
/** |
||||
* Logger that allows to separate log messages into several levels of |
||||
* significance and lets users and admins to access only the ones they want |
||||
* and/or receive notifications when they happen. |
||||
* Copyright 2020 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class LoggerService extends Service |
||||
config(AcediaLogger); |
||||
|
||||
// Log levels, available in Acedia. |
||||
enum LogLevel |
||||
{ |
||||
// For the purposes of "tracing" the code, when trying to figure out |
||||
// where exactly problems occurred. |
||||
// Should not be used in any released version of |
||||
// your packages/mutators. |
||||
LOG_Track, |
||||
// Information that can be used to track down errors that occur on |
||||
// other people's systems, that developer cannot otherwise pinpoint. |
||||
// Should be used with purpose of tracking a certain issue and |
||||
// not "just in case". |
||||
LOG_Debug, |
||||
// Information about important events that should be occurring under |
||||
// normal conditions, such as initializations/shutdowns, |
||||
// successful completion of significant events, configuration assumptions. |
||||
// Should not occur too often. |
||||
LOG_Info, |
||||
// For recoverable issues, anything that might cause errors or |
||||
// oddities in behavior. |
||||
// Should be used sparingly, i.e. player disconnecting might cause |
||||
// interruption in some logic, but should not cause a warning, |
||||
// since it is something expected to happen normally. |
||||
LOG_Warning, |
||||
// Use this for errors, - events that some operation cannot recover from, |
||||
// but still does not require your module to shut down. |
||||
LOG_Failure, |
||||
// Anything that does not allow your module or game to function, |
||||
// completely irrecoverable failure state. |
||||
LOG_Fatal |
||||
}; |
||||
|
||||
var private const string kfLogPrefix; |
||||
var private const string traceLevelName; |
||||
var private const string DebugLevelName; |
||||
var private const string infoLevelName; |
||||
var private const string warningLevelName; |
||||
var private const string errorLevelName; |
||||
var private const string fatalLevelName; |
||||
|
||||
var private config array< class<Manifest> > registeredManifests; |
||||
var private config bool logTraceInKFLog; |
||||
var private config bool logDebugInKFLog; |
||||
var private config bool logInfoInKFLog; |
||||
var private config bool logWarningInKFLog; |
||||
var private config bool logErrorInKFLog; |
||||
var private config bool logFatalInKFLog; |
||||
|
||||
var private array<string> traceMessages; |
||||
var private array<string> debugMessages; |
||||
var private array<string> infoMessages; |
||||
var private array<string> warningMessages; |
||||
var private array<string> errorMessages; |
||||
var private array<string> fatalMessages; |
||||
|
||||
public final function bool ShouldAddToKFLog(LogLevel messageLevel) |
||||
{ |
||||
if (messageLevel == LOG_Track && logTraceInKFLog) return true; |
||||
if (messageLevel == LOG_Debug && logDebugInKFLog) return true; |
||||
if (messageLevel == LOG_Info && logInfoInKFLog) return true; |
||||
if (messageLevel == LOG_Warning && logWarningInKFLog) return true; |
||||
if (messageLevel == LOG_Failure && logErrorInKFLog) return true; |
||||
if (messageLevel == LOG_Fatal && logFatalInKFLog) return true; |
||||
return false; |
||||
} |
||||
|
||||
public final static function LogMessageToKFLog |
||||
( |
||||
LogLevel messageLevel, |
||||
string message |
||||
) |
||||
{ |
||||
local string levelPrefix; |
||||
levelPrefix = default.kfLogPrefix; |
||||
switch (messageLevel) |
||||
{ |
||||
case LOG_Track: |
||||
levelPrefix = levelPrefix $ default.traceLevelName; |
||||
break; |
||||
case LOG_Debug: |
||||
levelPrefix = levelPrefix $ default.debugLevelName; |
||||
break; |
||||
case LOG_Info: |
||||
levelPrefix = levelPrefix $ default.infoLevelName; |
||||
break; |
||||
case LOG_Warning: |
||||
levelPrefix = levelPrefix $ default.warningLevelName; |
||||
break; |
||||
case LOG_Failure: |
||||
levelPrefix = levelPrefix $ default.errorLevelName; |
||||
break; |
||||
case LOG_Fatal: |
||||
levelPrefix = levelPrefix $ default.fatalLevelName; |
||||
break; |
||||
default: |
||||
} |
||||
Log(levelPrefix @ message); |
||||
} |
||||
|
||||
public final function LogMessage(LogLevel messageLevel, string message) |
||||
{ |
||||
switch (messageLevel) |
||||
{ |
||||
case LOG_Track: |
||||
traceMessages[traceMessages.length] = message; |
||||
case LOG_Debug: |
||||
debugMessages[debugMessages.length] = message; |
||||
case LOG_Info: |
||||
infoMessages[infoMessages.length] = message; |
||||
case LOG_Warning: |
||||
warningMessages[warningMessages.length] = message; |
||||
case LOG_Failure: |
||||
errorMessages[errorMessages.length] = message; |
||||
case LOG_Fatal: |
||||
fatalMessages[fatalMessages.length] = message; |
||||
default: |
||||
} |
||||
if (ShouldAddToKFLog(messageLevel)) |
||||
{ |
||||
LogMessageToKFLog(messageLevel, message); |
||||
} |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
// Log everything by default, if someone does not like it - |
||||
// he/she can disable it themselves. |
||||
logTraceInKFLog = true |
||||
logDebugInKFLog = true |
||||
logInfoInKFLog = true |
||||
logWarningInKFLog = true |
||||
logErrorInKFLog = true |
||||
logFatalInKFLog = true |
||||
// Parts of the prefix for our log messages, redirected into kf log file. |
||||
kfLogPrefix = "Acedia:" |
||||
traceLevelName = "Trace" |
||||
debugLevelName = "Debug" |
||||
infoLevelName = "Info" |
||||
warningLevelName = "Warning" |
||||
errorLevelName = "Error" |
||||
fatalLevelName = "Fatal" |
||||
} |
@ -0,0 +1,170 @@
|
||||
/** |
||||
* Set of tests related to `LoggerAPI`. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class TEST_LogMessage extends TestCase |
||||
abstract; |
||||
|
||||
// Short-hand for creating disposable `Text` out of a `string` |
||||
// We need it, since `P()` always returns the same value, which might lead to |
||||
// a conflict. |
||||
protected static function Text A(string message) |
||||
{ |
||||
return __().text.FromString(message); |
||||
} |
||||
|
||||
// Short-hand for quickly producing `LogMessage.Definition` |
||||
protected static function LoggerAPI.Definition DEF(string message) |
||||
{ |
||||
local LoggerAPI.Definition result; |
||||
result.m = message; |
||||
return result; |
||||
} |
||||
|
||||
protected static function TESTS() |
||||
{ |
||||
Context("Testing how `LogMessage` collects given arguments."); |
||||
Test_SimpleArgumentCollection(); |
||||
Test_ArgumentCollection(); |
||||
Test_ArgumentCollectionOrder(); |
||||
Test_TypedArgumentCollection(); |
||||
} |
||||
|
||||
protected static function Test_SimpleArgumentCollection() |
||||
{ |
||||
local LogMessage message; |
||||
Issue("`Text` arguments are not correctly pasted."); |
||||
message = LogMessage(__().memory.Allocate(class'LogMessage')); |
||||
message.Initialize(DEF("Message %1and%2: %3")); |
||||
message.Arg(A("umbra")).Arg(A("mumbra")).Arg(A("eleven! ")); |
||||
TEST_ExpectTrue( message.Collect().ToPlainString() |
||||
== "Message umbraandmumbra: eleven! "); |
||||
message = LogMessage(__().memory.Allocate(class'LogMessage')); |
||||
message.Initialize(DEF("%1 - was pasted.")); |
||||
message.Arg(A("Heheh")); |
||||
TEST_ExpectTrue(message.Collect().ToPlainString() == "Heheh - was pasted."); |
||||
message = LogMessage(__().memory.Allocate(class'LogMessage')); |
||||
message.Initialize(DEF("This %%%1 and that %2")); |
||||
message.Arg(A("one")).Arg(A("two")); |
||||
TEST_ExpectTrue( message.Collect().ToPlainString() |
||||
== "This %%one and that two"); |
||||
message = LogMessage(__().memory.Allocate(class'LogMessage')); |
||||
message.Initialize(DEF("%1%2")); |
||||
message.Arg(A("one")).Arg(A("two")); |
||||
TEST_ExpectTrue(message.Collect().ToPlainString() == "onetwo"); |
||||
message = LogMessage(__().memory.Allocate(class'LogMessage')); |
||||
message.Initialize(DEF("%1")); |
||||
message.Arg(A("only")); |
||||
TEST_ExpectTrue(message.Collect().ToPlainString() == "only"); |
||||
message = LogMessage(__().memory.Allocate(class'LogMessage')); |
||||
message.Initialize(DEF("Just some string.")); |
||||
TEST_ExpectTrue(message.Collect().ToPlainString() == "Just some string."); |
||||
} |
||||
|
||||
protected static function Test_ArgumentCollection() |
||||
{ |
||||
local LogMessage message; |
||||
Issue("`Text` arguments are not correctly collected after reset."); |
||||
message = LogMessage(__().memory.Allocate(class'LogMessage')); |
||||
message.Initialize(DEF("This %1 and that %2")); |
||||
message.Arg(A("one")).Arg(A("two")).Reset().Arg(A("huh")).Arg(A("muh")); |
||||
TEST_ExpectTrue( message.Collect().ToPlainString() |
||||
== "This huh and that muh"); |
||||
|
||||
Issue("`Text` arguments are not correctly collected after specifying" |
||||
@ "too many."); |
||||
message = LogMessage(__().memory.Allocate(class'LogMessage')); |
||||
message.Initialize(DEF("Just %1, %2, %3, %4 and %5")); |
||||
message.Arg(A("1")).Arg(A("2")).Arg(A("3")).Arg(A("4")).Arg(A("5")) |
||||
.Arg(A("6")).Arg(A("7")); |
||||
TEST_ExpectTrue( message.Collect().ToPlainString() |
||||
== "Just 1, 2, 3, 4 and 5"); |
||||
message = LogMessage(__().memory.Allocate(class'LogMessage')); |
||||
message.Initialize(DEF("Just")); |
||||
TEST_ExpectTrue(message.Arg(A("arg")).Collect().ToPlainString() == "Just"); |
||||
|
||||
Issue("`Text` arguments are not correctly collected after specifying" |
||||
@ "too little."); |
||||
message = LogMessage(__().memory.Allocate(class'LogMessage')); |
||||
message.Initialize(DEF("Just %1, %2, %3, %4 and %5")); |
||||
message.Arg(A("1")).Arg(A("2")).Arg(A("3")); |
||||
TEST_ExpectTrue( message.Collect().ToPlainString() |
||||
== "Just 1, 2, 3, and "); |
||||
message = LogMessage(__().memory.Allocate(class'LogMessage')); |
||||
message.Initialize(DEF("Maybe %1")); |
||||
TEST_ExpectTrue(message.Collect().ToPlainString() == "Maybe "); |
||||
} |
||||
|
||||
protected static function Test_ArgumentCollectionOrder() |
||||
{ |
||||
local LogMessage message; |
||||
Issue("`Text` arguments are not correctly collected if are not specified" |
||||
@ "in order."); |
||||
message = LogMessage(__().memory.Allocate(class'LogMessage')); |
||||
message.Initialize(DEF("This %2 and that %1")); |
||||
message.Arg(A("huh")).Arg(A("muh")); |
||||
TEST_ExpectTrue( message.Collect().ToPlainString() |
||||
== "This muh and that huh"); |
||||
message = LogMessage(__().memory.Allocate(class'LogMessage')); |
||||
message.Initialize(DEF("Just %5, %3, %4, %1 and %2")); |
||||
message.Arg(A("1")).Arg(A("2")).Arg(A("3")).Arg(A("4")).Arg(A("5")); |
||||
TEST_ExpectTrue( message.Collect().ToPlainString() |
||||
== "Just 5, 3, 4, 1 and 2"); |
||||
message = LogMessage(__().memory.Allocate(class'LogMessage')); |
||||
|
||||
Issue("`Text` arguments are not correctly collected if are not specified" |
||||
@ "in order and not enough of them was specified."); |
||||
message.Initialize(DEF("Just %5, %3, %4, %1 and %2")); |
||||
message.Arg(A("1")).Arg(A("2")).Arg(A("3")); |
||||
TEST_ExpectTrue( message.Collect().ToPlainString() |
||||
== "Just , 3, , 1 and 2"); |
||||
} |
||||
|
||||
protected static function Test_TypedArgumentCollection() |
||||
{ |
||||
local LogMessage message; |
||||
Issue("`int` arguments are not correctly collected."); |
||||
message = LogMessage(__().memory.Allocate(class'LogMessage')); |
||||
message.Initialize(DEF("Int: %1")); |
||||
TEST_ExpectTrue(message.ArgInt(-7).Collect().ToPlainString() |
||||
== "Int: -7"); |
||||
|
||||
Issue("`float` arguments are not correctly collected."); |
||||
message = LogMessage(__().memory.Allocate(class'LogMessage')); |
||||
message.Initialize(DEF("Float: %1")); |
||||
TEST_ExpectTrue(message.ArgFloat(3.14).Collect().ToPlainString() |
||||
== "Float: 3.14"); |
||||
|
||||
Issue("`bool` arguments are not correctly collected."); |
||||
message = LogMessage(__().memory.Allocate(class'LogMessage')); |
||||
message.Initialize(DEF("Bool: %1 and %2")); |
||||
TEST_ExpectTrue(message.ArgBool(true).ArgBool(false).Collect() |
||||
.ToPlainString() == "Bool: true and false"); |
||||
|
||||
Issue("`Class` arguments are not correctly collected."); |
||||
message = LogMessage(__().memory.Allocate(class'LogMessage')); |
||||
message.Initialize(DEF("Class: %1")); |
||||
TEST_ExpectTrue(message.ArgClass(class'M14EBRBattleRifle').Collect() |
||||
.ToPlainString() == "Class: KFMod.M14EBRBattleRifle"); |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
caseGroup = "Logger" |
||||
caseName = "LogMessage" |
||||
} |
Loading…
Reference in new issue