- new map change log to Slack channel - new "rally point" logging to Slack channel - new "rally point" poke options (e.g. custom message), closed #295 - new log options for WebSocket installations - added ship "mass" logging (backend only), #313 - added map logging to Slack, #326 - added "ESI error rate" limit detection - added "Monolog" as new logging library (Composer dependency) - added "Swiftmailer" as new eMail library (Composer dependency) - added Support for Redis session hander (performance boost) - improved character select panels (visible "online" status) - improved "activity logging" (more DB columns added to check) - improved eMail logging (HTML template support) - improved "delete map" now become "inactive" for some days before delete - improved character logout handling - improved /setup page for DB bootstrap (new button for DB create if not exists) - fixed broken ship tracking (ship name re-added) - fixed broken ship tracking for multiple chars on different browser tabs - fixed broken cursor coordinates, closed #518 - fixed null pointer "charactermodel.php->isActive():925" closed #529 - fixed broken "scroll offset", closed #533 closed #534 - Updated "validation" library JS v0.10.1 -> v0.11.9 - Updated ORM Mapper _Cortex_ v1.5.0-dev -> v1.5.0 - and many more....
549 lines
15 KiB
PHP
549 lines
15 KiB
PHP
<?php
|
|
/**
|
|
* Created by PhpStorm.
|
|
* User: exodu
|
|
* Date: 04.08.2017
|
|
* Time: 22:13
|
|
*/
|
|
|
|
namespace Lib\Logging;
|
|
|
|
|
|
use Lib\Monolog;
|
|
use Monolog\Logger;
|
|
|
|
abstract class AbstractLog implements LogInterface {
|
|
|
|
const ERROR_LEVEL = 'Invalid log level "%s"';
|
|
const ERROR_TAG = 'Invalid log tag "%s"';
|
|
const ERROR_HANDLER_KEY = 'Handler key "%s" not found in handlerConfig (%s)';
|
|
const ERROR_HANDLER_PARAMS = 'No handler parameters found for handler 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 = [];
|
|
|
|
/**
|
|
* 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;
|
|
|
|
|
|
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{
|
|
$params = [];
|
|
|
|
if($this->hasHandlerKey($handlerKey)){
|
|
switch($handlerKey){
|
|
case 'stream': $params = $this->getHandlerParamsStream();
|
|
break;
|
|
case 'zmq': $params = $this->getHandlerParamsZMQ();
|
|
break;
|
|
case 'mail': $params = $this->getHandlerParamsMail();
|
|
break;
|
|
case 'slackMap':
|
|
case 'slackRally':
|
|
$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;
|
|
}
|
|
|
|
/**
|
|
* @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());
|
|
}
|
|
|
|
/**
|
|
* @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\AbstractHandler ---------------------------------------------------------
|
|
protected function getHandlerParamsStream(): array{
|
|
$params = [];
|
|
if( !empty($conf = $this->handlerParamsConfig['stream']) ){
|
|
$params[] = $conf->stream;
|
|
}
|
|
|
|
return $params;
|
|
}
|
|
|
|
/**
|
|
* get __construct() parameters for ZMQHandler() call
|
|
* @return array
|
|
*/
|
|
protected function getHandlerParamsZMQ(): array {
|
|
$params = [];
|
|
if( !empty($conf = $this->handlerParamsConfig['zmq']) ){
|
|
// meta data (required by receiver socket)
|
|
$meta = [
|
|
'logType' => 'mapLog',
|
|
'stream'=> $conf->streamConf->stream
|
|
];
|
|
|
|
$context = new \ZMQContext();
|
|
$pusher = $context->getSocket(\ZMQ::SOCKET_PUSH);
|
|
$pusher->connect($conf->uri);
|
|
|
|
$params[] = $pusher;
|
|
$params[] = \ZMQ::MODE_DONTWAIT;
|
|
$params[] = false; // multipart
|
|
$params[] = Logger::toMonologLevel($this->getLevel()); // min level that is handled
|
|
$params[] = true; // bubble
|
|
$params[] = $meta;
|
|
}
|
|
|
|
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() 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;
|
|
}
|
|
|
|
/**
|
|
* send this Log to global log buffer storage
|
|
*/
|
|
public function buffer(){
|
|
Monolog::instance()->push($this);
|
|
}
|
|
|
|
|
|
} |