Skip to content

Commit

Permalink
Release Version 1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
JonisoftGermany committed Apr 4, 2021
0 parents commit a7dde87
Show file tree
Hide file tree
Showing 6 changed files with 377 additions and 0 deletions.
18 changes: 18 additions & 0 deletions ErrorReport2.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace ErrorReport2;

use SPFW\system\storage\SuperStorage;


/**
* ErrorReport2 Model
*
* @package ErrorReport2
* @version 2.0.0
*/
final class ErrorReport2 extends SuperStorage
{}


?>
191 changes: 191 additions & 0 deletions ErrorReport2Server.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
<?php

namespace ErrorReport2;

use SPFW\system\config\Config;
use SPFW\system\Controller;
use SPFW\system\JsonOutput;
use SPFW\system\routing\PostRequest;
use SPFW\system\routing\Request;


/**
* ErrorReport2 Server
*
* @package ErrorReport2
* @version 2.0.0
*/
final class ErrorReport2Server extends Controller
{
private const ER2_VERSION = '2.0.0';

private const ERROR_RESPONSE_CODE = 400;
private const SUCCESS_RESPONSE_CODE = 201;

private const REQUIRED_NODES = [
'authentication',
'general',
'environment',
'request',
'database',
'cookies',
'get',
'post',
'session'
];


private ErrorReport2ServerConfig $config;


public function __construct(string $method_name, Request $request)
{
parent::__construct( $method_name, $request);

$global_config = Config::get();
if ($global_config === null) {
throw new \RuntimeException('Unknown global configuration');
}

$config_traits = class_uses($global_config);
if (!\in_array(ErrorReport2ServerConfigTrait::class, $config_traits, true)) {
throw new \RuntimeException('Unknown global configuration');
}

/** @noinspection PhpUndefinedMethodInspection */
$er2_server_config = $global_config->getER2Config();

if ($er2_server_config === null) {
throw new \RuntimeException('Undefined ER2 configuration');
}

$this->config = $er2_server_config;
}

private function checkHttpMethod(Request $request) : void
{
if (!($request instanceof PostRequest)) {
throw new \RuntimeException('Invalid http method');
}
}

private function checkToken(array $json_structure) : void
{
$token_string = $json_structure['authentication']['token'];

if (!$this->config->authenticateToken($token_string)) {
throw new \InvalidArgumentException('Invalid token');
}
}

private function returnError() : JsonOutput
{
$error_json = [
'logging_accepted' => false
];

return new JsonOutput($error_json, self::ERROR_RESPONSE_CODE);
}

private function returnSuccess() : JsonOutput
{
$error_json = [
'logging_accepted' => true
];

return new JsonOutput($error_json, self::SUCCESS_RESPONSE_CODE);
}

public function listener(Request $request) : JsonOutput
{
try {
$this->checkHttpMethod($request);
$raw_post_payload = $this->rawPostData();
$json_structure = $this->unpackPayload($raw_post_payload);
$this->validateJson($json_structure);
$this->checkToken($json_structure);
$this->save($json_structure);
} catch (\Throwable $e) {
return $this->returnError();
}

return $this->returnSuccess();
}

private function prepareJsonInsertion(array $json_structure) : ?string
{
if ($json_structure === null) {
return null;
}

try {
return json_encode($json_structure, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
return 'JSON ERROR';
}
}

private function rawPostData() : string
{
return file_get_contents('php://input');
}

private function save(array $json_structure) : ErrorReport2
{
$er2_data = [
'service_id' => $json_structure['authentication']['service_id'],
'er2_client_version' => $json_structure['authentication']['er2_version'],
'er2_server_version' => self::ER2_VERSION,
'session_id' => $json_structure['general']['er2_session_id'],
'client_timestamp' => \DateTime::createFromFormat('Y-m-d\TH:i:s', $json_structure['general']['timestamp']),
'host_name' => $json_structure['general']['host_name'],
'host_os' => $json_structure['general']['host_os'],
'host_os_release' => $json_structure['general']['host_os_release'],
'host_os_version' => $json_structure['general']['host_os_version'],
'php_version' => $json_structure['general']['php_version'],
'php_mode' => $json_structure['general']['php_mode'],
'php_mem_usage' => $json_structure['general']['php_mem_usage'],
'debug_mode' => $json_structure['general']['debug_mode'],
'request_method' => $json_structure['request']['method'],
'request_domain' => $json_structure['request']['domain'],
'request_subdomain' => $json_structure['request']['subdomain'],
'request_tcp_port' => $json_structure['request']['tcp_port'],
'request_path' => $json_structure['request']['path'],
'request_cli' => $json_structure['request']['cli'],
'request_secure_connection' => $json_structure['request']['secure_connection'],
'environment' => $this->prepareJsonInsertion($json_structure['environment']),
'database' => $this->prepareJsonInsertion($json_structure['database']),
'cookies' => $this->prepareJsonInsertion($json_structure['cookies']),
'get' => $this->prepareJsonInsertion($json_structure['get']),
'post' => $this->prepareJsonInsertion($json_structure['post']),
'session' => $this->prepareJsonInsertion($json_structure['session']),
'errors' => $this->prepareJsonInsertion($json_structure['errors'] ?? null),
'throwable' => $this->prepareJsonInsertion($json_structure['throwable'] ?? null),
];

return ErrorReport2::create($er2_data);
}

/**
* @param string $raw_payload
*
* @return array Decoded json-string
* @throws \JsonException
*/
private function unpackPayload(string $raw_payload) : array
{
return json_decode($raw_payload, true, 20, JSON_THROW_ON_ERROR);
}

private function validateJson(array $json_structure) : void
{
foreach (self::REQUIRED_NODES as $node_name) {
if (!\array_key_exists($node_name, $json_structure)) {
throw new \InvalidArgumentException('Missing node "' . $node_name . '" in json-structure');
}
}
}
}


?>
38 changes: 38 additions & 0 deletions ErrorReport2ServerConfig.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace ErrorReport2;

use SPFW\system\config\IConfig;


/**
* ErrorReport2 Server Configuration
*
* @package ErrorReport2
* @version 2.0.0
*/
final class ErrorReport2ServerConfig implements IConfig
{
/** @var string[] $allowed_token */
private array $allowed_token = [];


public function addToken(string $token) : self
{
$this->allowed_token[] = $token;
return $this;
}

public function authenticateToken(string $token) : bool
{
return \in_array($token, $this->allowed_token, true);
}

public function checkConfig(bool $strict = false) : bool
{
return true;
}
}


?>
28 changes: 28 additions & 0 deletions ErrorReport2ServerConfigTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace ErrorReport2;


/**
* ErrorReport2 Config Trait
*
* @package ErrorReport2
* @version 2.0.0
*/
trait ErrorReport2ServerConfigTrait
{
private ?ErrorReport2ServerConfig $er2_config;

final public function getER2Config() : ?ErrorReport2ServerConfig
{
return $this->er2_config;
}

final public function setER2Config(ErrorReport2ServerConfig $er2_config) : void
{
$this->er2_config = $er2_config;
}
}


?>
65 changes: 65 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Error Report 2 (Server)

An error reporting service for SPFW.
Clients send error and exception logs to an API.

## Requirements

* SPFW >=1.3.0,
* PHP >=7.4 and
* MySQL >= 5.7 (or Maria DB >= 10.1).

__Note:__
While the client requires a minimum SPFW version 1.0.0, the serverside requires SPFW 1.3.0 or newer.

## Installation

1. Include this git-project as submodule in your modules-directory,
2. execute the sql-batch file _er2.sql_ in your database,
3. extend your config with ErrorReport2ServerConfigTrait,
4. add a ErrorReport2ServerConfig to your global config,
5. add a route for the listener to your routing repository and
6. configure ER2 Server (e.g. set up access token).

For step 3, a customizable config must exist.
If it does not exist yet, create a new class for your application config.
Extend SPFW\config\Config class.
Extend class by using _use_ keyword for ErrorReport2ServerConfigTrait.
Then replace SPFW\config\Config class by new application config class in _config.php_ file.

It is recommended to allow only secure connections.
This must be set in your web servers configuration.

__Example for 1.:__
```
cd src/modules
git submodule add URL_TO_REPOSITORY
```

__Example for 5.:__

```
Routing::addRoute((new StaticRoute(\ErrorReport2\ErrorReport2Server::class, 'listener'))
->setFile('/er2_listener'));
```

__Example for 6.:__

```
$config->setER2Config((new \ErrorReport2\ErrorReport2ServerConfig())->addToken('3915753d8765a0'));
```

## Configuration

### Required configuration

Every connection requires an access token.
It is recommended to create an individual token for each service you want to connect.

### Optional configuration

There are no optional parameter yet.

## Future development

A parallel project will be started to offer a web-gui for viewing and analyzing ER2 reports.
37 changes: 37 additions & 0 deletions er2.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
-- Error Report 2 Module
-- Version 2.0.0


CREATE TABLE ErrorReport2 (
`id` bigint unsigned NOT NULL auto_increment PRIMARY KEY,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`service_id` varchar(50),
`er2_client_version` varchar(11) NOT NULL,
`er2_server_version` varchar(11) NOT NULL,
`session_id` varchar(128) NOT NULL,
`client_timestamp` datetime NOT NULL,
`host_name` varchar(100) NOT NULL,
`host_os` varchar(100) NOT NULL,
`host_os_release` varchar(100) NOT NULL,
`host_os_version` varchar(100) NOT NULL,
`php_version` varchar(20) NOT NULL,
`php_mode` varchar(30) NOT NULL,
`php_mem_usage` bigint unsigned NOT NULL,
`debug_mode` tinyint unsigned NOT NULL COMMENT 'BOOL',
`request_method` enum('CONNECT', 'DELETE', 'GET', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'TRACE') NOT NULL,
`request_domain` varchar(255) NOT NULL,
`request_subdomain` varchar(255) NOT NULL,
`request_tcp_port` smallint unsigned NOT NULL,
`request_path` varchar(255) NOT NULL,
`request_cli` tinyint unsigned NOT NULL COMMENT 'BOOL',
`request_secure_connection` tinyint unsigned NOT NULL COMMENT 'BOOL',
`general` text NOT NULL,
`environment` text,
`database` text,
`cookies` text,
`get` text,
`post` text,
`session` text,
`errors` text,
`throwable` text
) ENGINE = InnoDB;

0 comments on commit a7dde87

Please sign in to comment.