Files
pathfinder/app/Lib/Logging/AbstractLog.php
Mark Friedrich 647bd7db58 - BC Break: _PHP_ namespaces changed (PSR-4 standard). The _root_ namespace for all _PF_ related scripts is Exodus4D\Pathfinder
- BC Break: Project folder structure changed. Removed `app/main` dir.
- BC Break: Core _PHP_ framework + dependencies moved into `composer.json` and are no longer part of this repo
2019-12-15 22:27:17 +01:00

630 lines
17 KiB
PHP

<?php
/**
* Created by PhpStorm.
* User: Exodus 4D
* Date: 04.08.2017
* Time: 22:13
*/
namespace Exodus4D\Pathfinder\Lib\Logging;
use Exodus4D\Pathfinder\Lib\Monolog;
use Monolog\Logger;
abstract class AbstractLog implements LogInterface {
/**
* error message invalid log level
*/
const ERROR_LEVEL = 'Invalid log level "%s"';
/**
* error message invalid log tag
*/
const ERROR_TAG = 'Invalid log tag "%s"';
/**
* error message unknown Handler key
*/
const ERROR_HANDLER_KEY = 'Handler key "%s" not found in handlerConfig (%s)';
/**
* error message undefined Handler params
*/
const ERROR_HANDLER_PARAMS = 'No handler parameters found for handler key "%s"';
/**
* error message unknown Processor key
*/
const ERROR_PROCESSOR_KEY = 'Processor key "%s" not found in processorConfig (%s)';
/**
* error message undefined Processor params
*/
const ERROR_PROCESSOR_PARAMS = 'No processor parameters found for processor key "%s"';
/**
* PSR-3 log levels
*/
const LEVEL = ['emergency', 'alert', 'critical', 'error', 'warning', 'notice', 'info', 'debug'];
/**
* log tags
*/
const TAG = ['danger', 'warning', 'information', 'success', 'primary', 'default'];
/**
* @var null|\Base
*/
protected $f3 = null;
/**
* log Handler type with Formatter type
* -> check Monolog::HANDLER and Monolog::FORMATTER
* @var array
*/
protected $handlerConfig = ['stream' => 'line'];
/**
* log Processors, array with either callable functions or Processor class with __invoce() method
* -> functions used to add "extra" data to a log
* @var array
*/
protected $processorConfig = ['psr' => null];
/**
* some handler need individual configuration parameters
* -> see $handlerConfig end getHandlerParams()
* @var array
*/
protected $handlerParamsConfig = [];
/**
* some processor need individual configuration parameters
* -> see $processorConfig end getProcessorParams()
* @var array
*/
protected $processorParamsConfig = [
'psr' => ['Y-m-d\A\TH:i:s.uP', false]
];
/**
* multiple Log() objects can be marked as "grouped"
* -> Logs with Slack Handler should be grouped by map (send multiple log data in once
* @var array
*/
protected $handlerGroups = [];
/**
* @var string
*/
protected $message = '';
/**
* @var string
*/
protected $action = '';
/**
* @var string
*/
protected $channelType = '';
/**
* log level from self::LEVEL
* -> private - use setLevel() to set
* @var string
*/
private $level = 'debug';
/**
* log tag from self::TAG
* -> private - use setTag() to set
* @var string
*/
private $tag = 'default';
/**
* log data (main log data)
* @var array
*/
private $data = [];
/**
* (optional) temp data for logger (will not be stored with the log entry)
* @var array
*/
private $tmpData = [];
/**
* buffer multiple logs with the same chanelType and store all at once
* @var bool
*/
private $buffer = true;
/**
* AbstractLog constructor.
* @param string $action
*/
public function __construct(string $action){
$this->setF3();
$this->action = $action;
// add custom log processor callback -> add "extra" (meta) data
$f3 = $this->f3;
$processorExtraData = function($record) use (&$f3){
$record['extra'] = [
'path' => $f3->get('PATH'),
'ip' => $f3->get('IP')
];
return $record;
};
// add log processor -> remove §tempData from log
$processorClearTempData = function($record){
$record['context'] = array_diff_key($record['context'], $this->getTempData());
return $record;
};
// init processorConfig. IMPORTANT: first processor gets executed at the end!
$this->processorConfig = ['cleaTempData' => $processorClearTempData] + [ 'addExtra' => $processorExtraData] + $this->processorConfig;
}
/**
* set $f3 base object
*/
public function setF3(){
$this->f3 = \Base::instance();
}
/**
* @param $message
*/
public function setMessage(string $message){
$this->message = $message;
}
/**
* @param string $level
* @throws \Exception
*/
public function setLevel(string $level){
if( in_array($level, self::LEVEL)){
$this->level = $level;
}else{
throw new \Exception( sprintf(self::ERROR_LEVEL, $level));
}
}
/**
* @param string $tag
* @throws \Exception
*/
public function setTag(string $tag){
if( in_array($tag, self::TAG)){
$this->tag = $tag;
}else{
throw new \Exception( sprintf(self::ERROR_TAG, $tag));
}
}
/**
* @param array $data
* @return LogInterface
*/
public function setData(array $data) : LogInterface {
$this->data = $data;
return $this;
}
/**
* @param array $data
* @return LogInterface
*/
public function setTempData(array $data) : LogInterface {
$this->tmpData = $data;
return $this;
}
/**
* add new Handler by $handlerKey
* set its default Formatter by $formatterKey
* @param string $handlerKey
* @param string|null $formatterKey
* @param \stdClass|null $handlerParams
* @return LogInterface
*/
public function addHandler(string $handlerKey, string $formatterKey = null, \stdClass $handlerParams = null) : LogInterface {
if(!$this->hasHandlerKey($handlerKey)){
$this->handlerConfig[$handlerKey] = $formatterKey;
// add more configuration params for the new handler
if(!is_null($handlerParams)){
$this->handlerParamsConfig[$handlerKey] = $handlerParams;
}
}
return $this;
}
/**
* add new handler for Log() grouping
* @param string $handlerKey
* @return LogInterface
*/
public function addHandlerGroup(string $handlerKey) : LogInterface {
if(
$this->hasHandlerKey($handlerKey) &&
!$this->hasHandlerGroupKey($handlerKey)
){
$this->handlerGroups[] = $handlerKey;
}
return $this;
}
/**
* @return array
*/
public function getHandlerConfig() : array {
return $this->handlerConfig;
}
/**
* get __construct() parameters for a given $handlerKey
* @param string $handlerKey
* @return array
* @throws \Exception
*/
public function getHandlerParams(string $handlerKey) : array {
if($this->hasHandlerKey($handlerKey)){
switch($handlerKey){
case 'stream': $params = $this->getHandlerParamsStream();
break;
case 'mail': $params = $this->getHandlerParamsMail();
break;
case 'socket': $params = $this->getHandlerParamsSocket();
break;
case 'slackMap':
case 'slackRally':
case 'discordMap':
case 'discordRally':
$params = $this->getHandlerParamsSlack($handlerKey);
break;
default:
throw new \Exception(sprintf(self::ERROR_HANDLER_PARAMS, $handlerKey));
}
}else{
throw new \Exception(sprintf(self::ERROR_HANDLER_KEY, $handlerKey, implode(', ', array_flip($this->handlerConfig))));
}
return $params;
}
/**
* @return array
*/
public function getHandlerParamsConfig() : array {
return $this->handlerParamsConfig;
}
/**
* @return array
*/
public function getProcessorConfig() : array {
return $this->processorConfig;
}
/**
* get __construct() parameters for a given $processorKey
* @param string $processorKey
* @return array
* @throws \Exception
*/
public function getProcessorParams(string $processorKey) : array {
if($this->hasProcessorKey($processorKey)){
switch($processorKey){
case 'psr': $params = $this->getProcessorParamsPsr();
break;
default:
throw new \Exception(sprintf(self::ERROR_PROCESSOR_PARAMS, $processorKey));
}
}else{
throw new \Exception(sprintf(self::ERROR_PROCESSOR_KEY, $processorKey, implode(', ', array_flip($this->processorConfig))));
}
return $params;
}
/**
* @return string
*/
public function getMessage() : string {
return $this->message;
}
/**
* @return string
*/
public function getAction() : string {
return $this->action;
}
/**
* @return string
*/
public function getChannelType() : string {
return $this->channelType;
}
/**
* @return string
*/
public function getChannelName() : string {
return $this->getChannelType();
}
/**
* @return string
*/
public function getLevel() : string {
return $this->level;
}
/**
* @return string
*/
public function getTag() : string {
return $this->tag;
}
/**
* @return array
*/
public function getData() : array {
return $this->data;
}
/**
* @return array
*/
public function getContext() : array {
$context = [
'data' => $this->getData(),
'tag' => $this->getTag()
];
// add temp data (e.g. used for $message placeholder replacement
$context += $this->getTempData();
return $context;
}
/**
* @return array
*/
protected function getTempData() : array {
return $this->tmpData;
}
/**
* @return array
*/
public function getHandlerGroups() : array {
return $this->handlerGroups;
}
/**
* get unique hash for this kind of logs (channel) and same $handlerGroups
* @return string
*/
public function getGroupHash() : string {
$groupName = $this->getChannelName();
if($this->isGrouped()){
$groupName .= '_' . implode('_', $this->getHandlerGroups());
}
return $this->f3->hash($groupName);
}
/**
* @param string $handlerKey
* @return bool
*/
public function hasHandlerKey(string $handlerKey) : bool {
return array_key_exists($handlerKey, $this->handlerConfig);
}
/**
* @param string $handlerKey
* @return bool
*/
public function hasHandlerGroupKey(string $handlerKey) : bool {
return in_array($handlerKey, $this->getHandlerGroups());
}
/**
* @param string $processorKey
* @return bool
*/
public function hasProcessorKey(string $processorKey) : bool {
return array_key_exists($processorKey, $this->processorConfig);
}
/**
* @return bool
*/
public function hasBuffer() : bool {
return $this->buffer;
}
/**
* @return bool
*/
public function isGrouped() : bool {
return !empty($this->getHandlerGroups());
}
/**
* remove all group handlers and their config params
*/
public function removeHandlerGroups(){
foreach($this->getHandlerGroups() as $handlerKey){
$this->removeHandlerGroup($handlerKey);
}
}
/**
* @param string $handlerKey
*/
public function removeHandlerGroup(string $handlerKey){
unset($this->handlerConfig[$handlerKey]);
unset($this->handlerParamsConfig[$handlerKey]);
}
// Handler parameters for Monolog\Handler\* instances -------------------------------------------------------------
/**
* @return array
*/
protected function getHandlerParamsStream() : array {
$params = [];
if( !empty($conf = $this->handlerParamsConfig['stream']) ){
$params[] = $conf->stream;
$params[] = Logger::toMonologLevel($this->getLevel()); // min level that is handled;
$params[] = true; // bubble
$params[] = 0666; // permissions (default 644)
}
return $params;
}
/**
* get __construct() parameters for SwiftMailerHandler() call
* @return array
*/
protected function getHandlerParamsMail() : array {
$params = [];
if( !empty($conf = $this->handlerParamsConfig['mail']) ){
$transport = (new \Swift_SmtpTransport())
->setHost($conf->host)
->setPort($conf->port)
->setEncryption($conf->scheme)
->setUsername($conf->username)
->setPassword($conf->password)
->setStreamOptions([
'ssl' => [
'allow_self_signed' => true,
'verify_peer' => false
]
]);
$mailer = new \Swift_Mailer($transport);
// callback function used instead of Swift_Message() object
// -> we want the formatted/replaced message as subject
$messageCallback = function($content, $records) use ($conf){
$subject = 'No Subject';
if(!empty($records)){
// build subject from first record -> remove "markdown"
$subject = str_replace(['*', '_'], '', $records[0]['message']);
}
$jsonData = @json_encode($records, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$message = (new \Swift_Message())
->setSubject($subject)
->addPart($jsonData)
->setFrom($conf->from)
->setTo($conf->to)
->setContentType('text/html')
->setCharset('utf-8')
->setMaxLineLength(1000);
if($conf->addJson){
$jsonAttachment = (new \Swift_Attachment())
->setFilename('data.json')
->setContentType('application/json')
->setBody($jsonData);
$message->attach($jsonAttachment);
}
return $message;
};
$params[] = $mailer;
$params[] = $messageCallback;
$params[] = Logger::toMonologLevel($this->getLevel()); // min level that is handled
$params[] = true; // bubble
}
return $params;
}
/**
* get __construct() parameters for SocketHandler() call
* @return array
*/
protected function getHandlerParamsSocket() : array {
$params = [];
if( !empty($conf = $this->handlerParamsConfig['socket']) ){
// meta data (required by receiver socket)
$meta = [
'logType' => 'mapLog',
'stream'=> $conf->streamConf->stream
];
$params[] = $conf->dsn;
$params[] = Logger::toMonologLevel($this->getLevel());
$params[] = true;
$params[] = $meta;
}
return $params;
}
/**
* get __construct() params for SlackWebhookHandler() call
* @param string $handlerKey
* @return array
*/
protected function getHandlerParamsSlack(string $handlerKey) : array {
$params = [];
if( !empty($conf = $this->handlerParamsConfig[$handlerKey]) ){
$params[] = $conf->slackWebHookURL;
$params[] = $conf->slackChannel;
$params[] = $conf->slackUsername;
$params[] = true; // $useAttachment
$params[] = $conf->slackIcon;
$params[] = true; // $includeContext
$params[] = false; // $includeExtra
$params[] = Logger::toMonologLevel($this->getLevel()); // min level that is handled
$params[] = true; // $bubble
//$params[] = ['extra', 'context.tag']; // $excludeFields
$params[] = []; // $excludeFields
}
return $params;
}
// Processor parameters for Monolog\Processor\* instances ---------------------------------------------------------
/**
* get __construct() params for PsrLogMessageProcessor() call
* @return array
*/
protected function getProcessorParamsPsr() : array {
return !empty($conf = $this->processorParamsConfig['psr']) ? $conf : [];
}
/**
* send this Log to global log buffer storage
*/
public function buffer(){
if( !empty($this->handlerParamsConfig) ){
Monolog::instance()->push($this);
}
}
}