terrazza/logger

logger

1.0.0 2022-05-23 20:22 UTC

This package is auto-updated.

Last update: 2024-04-24 00:56:37 UTC


README

This component is an implementation of PSR/Log standard with some extensions.

Structure

  1. the Logger component
    has to be initialized with 0-n ChannelHandler
    and provides the common known methods:
    • warning
    • error
    • notice
    • ...
  2. the ChannelHandler component
    is responsible to determine
    1. the target/writer (required)
    2. the recordFormatter (required)
    3. a channelFilter (optional)
    4. all related LogHandler for this channel
  3. the LogHandler component
    determines
    • the logLevel
    • the format (optional, default: from ChannelHandler->recordFormatter)

The Terrazza/Logger component differ to the common PSR/Log implementation in handling the "Format". The Writer Component handles multiple "rows" and combines it. Within this difference its possible to forward a transformed format and keep his keys per row.
For example: write the message/format into a json object or db.

Object/Classes

  1. Logger
    1. method: registerChannelHandler
    2. method: registerExceptionHandler
    3. method: registerErrorHandler
    4. method: registerFatalHandler
    5. method: setExceptionFileName
    6. constructor: context (array)
  2. Handler
    1. ChannelHandler
    2. LogHandler
  3. LogRecord
    1. LogRecordTrace
  4. LogRecordFormatter
  5. LogHandlerFilter
  6. Converter
  7. Install
  8. Requirements
  9. Examples

Logger

The Logger object/methods is/are close to the common PSR implementations but!
The Logger is initialized with channelHandler(s) and not Handler

each channelHandler executes only one logHandler

method: registerChannelHandler

adds a channelHandler to the logger (not immutable).

method: registerExceptionHandler

register a callback for php exception handler.

well-developed projects should handle/cover all exceptions by themselves, but ;-)

method: registerErrorHandler

register a callback for php error handler.

sometime this kind of errors can't be catched without this workaround

method: registerFatalHandler

register a callback for php shutdown.

sometime this kind of errors can't be catched without this workaround

method: setExceptionFileName

The method:addMessage itself is covered with try/catch.
The catch handler writes the Exception.Message to a file which can be set with the method setExceptionFileName.

notice:
default: php://stderr

constructor: context (array)

The logger can be initialized, next to the name, with an initialized context.
This context can be addressed separately.

example of usage
a class is injected with the component and inside the constructor

$logger = new Logger("name", ["user" => "Value"]);
$logger->notice("hello", ["my" => "value"]);
$format = ["{Context.my} {iContext.user}"];

Handler

ChannelHandler

A ChannelHandler collects LogHandler to the same channel and provides

  • the same writer
  • the same formatter
    for each LogHandler.

A ChannelHandler can be registered through the Logger with

method: getWriter
method: getFormatter
method: getFilter
method: getLogHandler (LogHandlerInterface[])
method: pushLogHandler

Method to add a new LogHandler.
The logHandler-array will be key-sorted, to prevent multiple write transaction for different LogLevels.

method: getEffectedHandler

return the matched LogHandler for a given LogRecord.

method: writeRecord

for a passed LogHandler the record will be

  • formatted
  • and written to the Writer

LogHandler

The SingleHandler provides the common way to create a handler for a Logger. The only difference to the common implementation:

  • instead of logLevel
  • the SingleHandler has to be injected within a Channel

LogRecord

Against the common PSR implementation our component deals with an object and not an array.
LogRecord properties:

  • logDate (\Datetime)
  • loggerName (string)
  • logLevel (int)
  • logMessage (string)
  • memUsed (int)
  • memAllocated (int)
  • LogRecordTrace
  • context (array)
  • initContext (array)

additional, the object provides

  • logLevelName (string)

method/static createRecord

this method is used inside Logger to create a new LogRecord object.

method: getToken()

this method is used in the LogRecordFormatterInterface to get the LogRecord "encoded". Every element can be accessed through his "format" e.g. {Level}{LevelName}{Context.name}

return [
  'Date'         => $this->getLogDate(),
  'Level'        => $this->getLogLevel(),
  'LevelName'    => $this->getLogLevelName(),
  'LoggerName'   => $this->getLoggerName(),
  'MemUsed'      => $this->getMemUsed(),
  'MemAllocated' => $this->getMemAllocated(),
  'Message'      => $this->getLogMessage(),
  'Context'      => $this->getContext(),
  'iContext'     => $this->getInitContext(),
  'Trace'        => [
    "Namespace"    => $this->getTrace()->getNamespace(),
    "Line"         => $this->getTrace()->getLine(),
    "Classname"    => $this->getTrace()->getClassname(),
    "Function"     => $this->getTrace()->getFunction(),
    "Method"       => $this->getTrace()->getClassname()."::".$this->getTrace()->getFunction(),
    "sMethod"      => basename($this->getTrace()->getClassname())."::".$this->getTrace()->getFunction(),  
  ]
]

LogRecordTrace

The LogRecordTrace object is generated in the Logger during a LogRecord is created.
Base on debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) every Record get additional properties:

  • Namespace
  • Classname
  • Function
  • Line

LogRecordFormatter

The LogRecordFormatter converts/maps a record to an array
. Initialized properties:

  • NonScalarConverterInterface
  • format (array)
  • valueConverter (array, optional)

NonScalarConverter (NonScalarConverterInterface)

The NonScalarConverter convert a nonScalar value (e.g. from Context) into a string.
Actually the provided class NonScalarJsonEncode use json_encode and prefix it with the attribute name.

The NonScalarConverter is used, when a formatter-line includes a nonScalar and a scalar content.

example of usage:

use Terrazza\Component\Logger\Converter\NonScalar\NonScalarJsonConverter;

$record = ["message" => "myMessage", "key" => ["value1", "value2"]];
echo (new NonScalarJsonEncode())->getValue($context["key"]); // key:{"value1", "value2"}

// in context of the formatter it will be
$format = ["{Message}:{Context.key}"];                 // ... myMessage:key:{"value1", "value2"}
$format = ["Context" => "{Message}:{Context.key}"];    // ... myMessage:key:{"value1", "value2"}

a ValueConverter (LogRecordValueConverterInterface) can be used to convert special value based on his key.

method: pushConverter

to map/convert a special value, based on his key, push a special converter.
This converter has to fulfill LogRecordValueConverterInterface.

method: formatRecord

Maps the Record against the $format and returns a mapped array.
Unknown patterns (e.g. {undefined}) are removed from the response.
Empty "Lines" are also removed.

example

The example uses an additional ValueConverter for the Record value "Date".

use DateTime;
use Terrazza\Component\Logger\LogRecordValueConverterInterface;
use Terrazza\Component\Logger\LOgRecord;
use Terrazza\Component\Logger\Formatter\LogRecordFormatter;
use Terrazza\Component\Logger\Converter\NonScalar\NonScalarJsonEncode;

class RecordTokenValueDate implements LogRecordValueConverterInterface {
    private string $dateFormat;
    public function __construct(string $dateFormat="Y-m-d H:i:s.u") {
        $this->dateFormat                           = $dateFormat;
    }
    public function getValue($value) {
        return $value->format($this->dateFormat);
    }
}

$formatter = new LogRecordFormatter(
   new NonScalarJsonEncode,
   ["Date", "Message"]
);
$formatter->pushConverter("Date", new RecordTokenValueDate);   

$record  = LogRecord::create("LoggerName", 100, "myMessage");
var_dump($formatter->formatRecord($record)); 
/*
[
   "Date" => 2022-12-31 23:59:01,
   "Message" => "myMessage"
]
*/

LogHandlerFilter

LogHandler can have a LogHandlerFilter.
Properties:

  • include (array, optional)
  • exclude (array, optional)
  • start (array, optional)

method: isHandling (string $callerNamespace) : bool

include
preg_match callerNamespace against include patterns
exclude
preg_match callerNamespace against exclude patterns
start
preg_match callerNamespace against start patterns
if preg_match is true all further isHandling will be true. (exclude filter overrules start)

Converter

A LogRecord will be first converted/mapped with the RecordFormatter
Afterwards, it depends on the target/writer the array has to be formatted again.

We actually include/provide two Converter:

  • convert to a string, json type
  • convert to a string, e.g. for console logging In any case the Converter is injected into the Writer.

FormattedRecordFlat

Converts the mapped LogRecord into a string within a delimiter for each row.
For nonScalar values we use json_encode to convert the value.

method: setNonScalarPrefix(string $delimiter)

by using this method nonScalar values will be prefixed with the dataKey and the delimiter.
arguments:

  • delimiter (string, required)
  • encodingFlags (int, optional)

example of usage:

use Terrazza\Component\Logger\Converter\FormattedRecord\FormattedRecordFlat;

$formatter = new FormattedRecordFlat("|",0);
echo $formatter->convert(["message" => "myMessage", "context" => ["k" => "v"]); 
//myMessage|{"k":"v"}

$formatter->setNonScalarPrefix(":");
echo $formatter->convert(["message" => "myMessage", "context" => ["k" => "v"]); 
//myMessage|context:{"k":"v"}

FormattedRecordJson

Converts the mapped LogRecord into a string by using json_encode.
arguments:

  • encodingFlags (int, optional)

example of usage:

use Terrazza\Component\Logger\Converter\FormattedRecord\FormattedRecordJson;

$formatter = new FormattedRecordJson(0);
echo $formatter->convert(["message" => "myMessage", "context" => ["k" => "v"]); 
//{"message" : "myMessage", "context": {"k":"v"}}

Writer

StreamFile

Save converted record to a file.
arguments:

  • converter (IFormattedRecordConverter, required)
  • filename (string required)
  • flags (int, optional, default: 0)

the converter should convert the formatted LogRecord into a string.

use Terrazza\Component\Logger\Writer\StreamFile;
use Terrazza\Component\Logger\Converter\FormattedRecord\FormattedRecordFlat;

$logFile    = "log.txt";
@unlink($logFile);
$converter  = new FormattedRecordFlat("|",0);
$writer     = new StreamFile($converter, $logFile);
$writer->write(["message" => "myMessage", "context" => ["k" => "v"]);

$logContent = file_get_contents($logFile);
echo $logContent;
//{"message" : "myMessage", "context": {"k":"v"}}

How to install

Install via composer

composer require terrazza/logger

Requirements

  • php >= 7.4

composer packages

  • psr/log

Examples

1. create a ChannelHandler

use Terrazza\Component\Logger\Converter\FormattedRecord\FormattedRecordFlat;
use Terrazza\Component\Logger\Writer\StreamFile;
use Terrazza\Component\Logger\Formatter\RecordFormatter;
use Terrazza\Component\Logger\Converter\NonScalar\NonScalarJsonEncode;
use Terrazza\Component\Logger\Handler\ChannelHandler;

$writeConverter   = new FormattedRecordFlat("|",0); 
$writer           = new StreamFile($writeConverter, "test.log");
$formatter        = new RecordFormatter(new NonScalarJsonEncode(), [
   "Message" => "{Level}-{Message}-{Context.pid}"
]); 
$channelHandler   = new ChannelHandler($writer, $formatter);

2. create a LogHandler

notice
next code lines depends on previous example... (create a ChannelHandler)

use Terrazza\Component\Logger\Handler\LogHandler;
use Terrazza\Component\Logger\Logger;

$handler          = new LogHandler(Logger::WARNING);
// push handler into previouse create channelHandler
$channelHandler->pushLogHandler($handler);

3. create within a handler

notice:
next code lines depends on previous example... (create a LogHandler)

use Terrazza\Component\Logger\Logger;

// additinal we initialize the Context with pid = getmypid
// the formatter uses {Context.pid} and will print it

$logger           = new Logger("loggerName", ["pid" => "myPID"], $channelHandler);
$logger->error($message = "message");

create and registerChannelHandler

use Terrazza\Component\Logger\Logger;

$logger           = new Logger("loggerName", ["pid" => getmypid()]);
$logger           = $logger->registerChannelHandler($channelHandler);
$logger->error($message = "message");

Usage of Logger

produce a NOTICE

$logger->notice("myMessage");
// by using our examples above this message will not be printed
// ...cause the logLevel for the Handler is WARNING

produce an ERROR

$logger->error("myMessage"); 
// output to file will be: 400-myMessage-myPID