Commit d1e4119e authored by Bertrand Gauthier's avatar Bertrand Gauthier
Browse files

Premier commit

parents
Loading
Loading
Loading
Loading
Loading

.gitlab-ci.yml

0 → 100644
+24 −0
Original line number Diff line number Diff line
image: registre.unicaen.fr:5000/unicaen-dev-php7.3-apache

stages:
- publish
- tests

cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
  - vendor/

#unit-tests:
#  stage: tests
#  script:
#    - composer install --no-interaction --no-suggest --no-progress
#    - php vendor/bin/phpunit --coverage-text=coverage.txt --colors=never
#  artifacts:
#    paths:
#      - coverage.txt

update-satis:
  stage: publish
  script:
    - curl https://gest.unicaen.fr/packagist/update

CHANGELOG.md

0 → 100644
+6 −0
Original line number Diff line number Diff line
CHANGELOG
=========

1.0.0
-----
- Premmière version stable.

Module.php

0 → 100644
+71 −0
Original line number Diff line number Diff line
<?php

namespace UnicaenLivelog;

use Laminas\Config\Factory as ConfigFactory;
use Laminas\Console\Adapter\AdapterInterface;
use Laminas\Filter\Word\CamelCaseToDash;
use Laminas\ModuleManager\Feature\ConsoleBannerProviderInterface;
use Laminas\ModuleManager\Feature\ConsoleUsageProviderInterface;
use Laminas\Mvc\ModuleRouteListener;
use Laminas\Mvc\MvcEvent;
use Laminas\Stdlib\Glob;

class Module implements ConsoleBannerProviderInterface, ConsoleUsageProviderInterface
{
    public function onBootstrap(MvcEvent $e)
    {
        $eventManager        = $e->getApplication()->getEventManager();
        $moduleRouteListener = new ModuleRouteListener();
        $moduleRouteListener->attach($eventManager);
    }

    public function getConfig()
    {
        $paths = array_merge(
            [__DIR__ . '/config/module.config.php'],
            Glob::glob(__DIR__ . '/config/others/{,*.}{config}.php', Glob::GLOB_BRACE)
        );

        return ConfigFactory::fromFiles($paths);
    }

    public function getAutoloaderConfig(): array
    {
        return array(
            'Laminas\Loader\StandardAutoloader' => array(
                'namespaces' => array(
                    __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
                ),
            ),
        );
    }

    /**
     * @inheritDoc
     */
    public function getConsoleBanner(AdapterInterface $console): ?string
    {
        return 'Module ' . __NAMESPACE__;
    }

    /**
     * @inheritDoc
     */
    public function getConsoleUsage(AdapterInterface $console)
    {
        return [
            /**
             * @see ConsoleController::runSocketsAction()
             */
            "unicaen-livelog run-sockets [--verbose=<verbose>]" => "Lance les 2 serveurs (socket et websocket).",
            [ '<verbose>', "Mode verbeux (1) ou pas (0).", "Facultatif"],

            /**
             * @see ConsoleController::testAction()
             */
            "unicaen-livelog test [--message=<message>]" => "Teste l'envoi d'un message au serveur de socket.",
            [ '<message>', "Message de log", "Facultatif"],
        ];
    }
}

README.md

0 → 100644
+311 −0
Original line number Diff line number Diff line
Module `unicaen/livelog`
=========================

Le module `unicaen/livelog` propose une solution pour afficher des logs "en live" dans le navigateur web,
çàd au fur et à mesure qu'ils sont "publiés".

Ceci est réalisé à l'aide de :
- un `Logger` Monolog affublé d'un `LiveLogSocketLogHandler` qui envoie chaque ligne de log à une socket Unix ;
- une socket Unix recevant chaque ligne de log envoyée par le `LiveLogSocketHandler` et les passant à une websocket ;
- une websocket envoyant aux clients (navigateurs) connectés et identifiés chaque ligne de log.


Installation
------------

```bash
composer require unicaen/livelog
```


Configuration
-------------

Copiez le fichier `config/unicaen-livelog.local.php.dist` dans le répertoire `config/autoload` de votre appli sans 
son extension `.dist`, exmeple :

```bash
cp -n vendor/unicaen-livelog/config/unicaen-livelog.local.php.dist config/autoload/unicaen-livelog.local.php
```

```php
    'unicaen-livelog' => [
        /**
         * Configuration de la websocket.
         */
        'websocket' => [
            /**
             * Adresse "privée" à laquelle écoute la websocket.
             */
            'private_url' => '0.0.0.0:7443',

            /**
             * Adresse "public" à laquelle la websocket est accessible par les clients (navigateurs).
             * Dans cet exemple, un navigateur web pourrait se connecter à l'adresse 'wss://serveur.unicaen.fr/livelog'. 
             */
            'public_url' => '/livelog',

            /**
             * Mode verbeux ou pas.
             */
            'verbose' => false,
        ],
        /**
         * Configuration de la socket.
         */
        'socket' => [
            /**
             * Chemin de la socket Unix.
             */
            'path' => 'unix:///tmp/unicaen_livelog.sock',

            /**
             * Mode verbeux ou pas.
             */
            'verbose' => false,
        ],
```


Lancement des sockets
---------------------

Pour lancer la websocket et la socket en mode verbeux, ouvrez un terminal et lancez ceci 
**en tant que root** :

```bash
runuser -u www-data -- php public/index.php unicaen-livelog run-sockets --verbose 1
```

Si vous rencontrez l'erreur suivante, supprimez la socket Unix (ex : `rm /tmp/unicaen_livelog.sock`) :

```
RuntimeException
Failed to listen on Unix domain socket "unix:///tmp/unicaen_livelog.sock": Unknown error
```


Test de la socket
-----------------

Pour tester la socket, ouvrez un 2e terminal et lancez ceci :

```bash
runuser -u www-data -- php public/index.php unicaen-livelog test
```

Et vous devriez voir dans le terminal de lancement un truc qui ressemble à ça :

```
[socket]>    DATA {"formatted":"[2022-02-01T15:09:39.070488+01:00] mylogger.INFO: Ceci est un test \u00e0 2$ ! D'ac ? ~ % [] []\n","clientId":"client_61f93f2312813"}
[websocket]> SEND : Stop, client client_61f93f2312813 inconnu !
```

Où l'on voit que la socket reçoit bien le message de test mis en forme par le logger (date, etc.)
On voit ensuite que la websocket se plaint qu'elle ne connait pas le client `client_61f93f2312813`. 
C'est normal : seuls les clients (navigateurs) disposant d'un identifiant officiel fourni par l'application sont 
reconnus et peuvent recevoir les logs.


Test de la websocket
--------------------

Avant de pouvoir tester la websocket, il faut [configurer Apache](#Configurer-Apache).

Plus difficile de tester la websocket si ce n'est en ouvrant une page de votre appli qui établie une connexion. 
C'est possible d'utiliser `websocat` (https://github.com/vi/websocat).

Inspirez-vous de cette commande pour vous connecter à la websocket :

```bash
websocat --insecure --protocol "livelog" 'wss://localhost.unicaen.fr:8003/livelog?client=client_1234'
```

Ensuite tapez au clavier `Un message de log !` suivi de la touche "Entrée" puis "CTRL+C" pour stopper `websocat`.
Vous devriez voir dans le terminal de lancement un truc qui ressemble à ça :

```
[websocket]> OPEN : client_1234
[websocket]> MESSAGE : un message de log !

[websocket]> CLOSE : client_1234
```


Configurer Apache
-----------------

Pour que la websocket soit accessible depuis un navigateur web, il faut faire un peu de config Apache.

- Le module `proxy_wstunnel` doit être activé, exemple :

```bash
a2enmod proxy_wstunnel
```

- L'URL d'accès à la websocket (ex : `wss://localhost.unicaen.fr:8003/livelog`) doit être accessible de façon sécurisée, 
  voici un exemple de conf à mettre dans `/etc/apache2/conf-available/livelog.conf` :

```apacheconf
# Websocket pour unicaen/livelog.
# ==> accessible de l'extérieur à l'adresse wss://serveur.unicaen.fr/livelog
SSLProxyEngine On
ProxyPass        /livelog  ws://0.0.0.0:7443
ProxyPassReverse /livelog  ws://0.0.0.0:7443
```

NB : `0.0.0.0:7443` doit correspondre à la clé `'private_url'` que vous avez spécifiée dans la config du module ; 
idem pour `/livelog` et la clé `'public_url'`.

- Ensuite, cette conf doit être activée :

```bash
a2enconf livelog
```


Code
----

### Côté contrôleur

Exemple d'une page "voir" (action `voirAction`) affichant les caractéristiques d'un import de données et contenant 
un bouton "Lancer l'import" (action `lancerAction`).

L'action `lancerAction` lance l'import via un service qui utilise un `Logger` pour produire des logs. 
Ce sont ces lignes de logs que l'on veut afficher au fur et à mesure qu'elles sont produites.

- `ImportController.php`

```php
use Laminas\Mvc\Controller\AbstractActionController;
use Monolog\Logger;
use UnicaenLiveLog\Log\LivelogSocketLogHandler;

class ImportController extends AbstractActionController
{
    use LivelogSocketLogHandlerAwareTrait;
        
    public function voirAction(): array
    {
        //...
    }
        
    public function lancerAction(): bool
    {
        //...
        
        $logger = new Logger('livelog');
        $logger->pushHandler($this->livelogSocketHandler);
        $this->importService->setLogger($logger);
        $result = $this->importService->runImport($import);
        
        return false;
    }

    //...
}
```

- `ImportControllerFactory.php`

```php
use Psr\Container\ContainerInterface;
use UnicaenLivelog\Log\LivelogSocketLogHandler;

class ImportControllerFactory
{
    function __invoke(ContainerInterface $container): ImportController
    {
        $controller = new ImportController();
        
        $livelogSocketHandler = $container->get(LivelogSocketLogHandler::class);
        $controller->setLivelogSocketHandler($livelogSocketHandler);
       
        //...
        
        return $controller;
    }
}
```

Notez bien le `LivelogSocketLogHandler` injecté dans le contrôleur puis branché sur un `Logger` afin que
chaque ligne de log soit envoyée à la socket.

### Côté vues

Dans la vue `voir.phtml` associée à `voirAction`, vous aurez typiquement un bouton déclenchant l'opération dont 
les logs vous intéressent. Vous devez intercepter le clic sur ce bouton afin de lancer la requête en mode AJAX 
(car il faut rester sur la page courante afin de voir les logs !) On le fait dans cet exemple uniquement lorsque la
connexion à la websocket peut être établie, permettant ainsi de retomber sur le fonctionnement non-AJAX éventuelelement
supporté.

Exemple :

```html
<a class="btn btn-warning"
   id="lancer"
   href="<?php echo $this->url(
       'unicaen-db-import/import/lancer',
       ['name' => $import->getName()]
   ) ?>"><span class="fas fa-play"></span> Lancer l'import</a>

<script>
  $(function() {
    // `conn` est de type `WebSocket` (https://developer.mozilla.org/fr/docs/Web/API/WebSocket)
    let conn = $("#livelog-div").data('conn');
    conn.addEventListener('open', function(e) {
      $("#lancer").on('click', function(event) {
        let trigger = $(this).addClass('disabled');
        $.get(trigger.attr("href"), function() {
          trigger.removeClass('disabled');
        });
      });
    });
  });
</script>
```

Vous devez créer le conteneur dans lequel s'afficheront les logs (et quelques autres petites choses) 
avec l'aide de vue fournie par le module.

```php
<?php
/** @var \UnicaenLivelog\View\Helper\LivelogPanelHelper $livelogPanelHelper */
$livelogPanelHelper = $this->plugin('livelogPanel');
$livelogPanelHelper->setDivId($divId = 'livelog-div');
echo $livelogPanelHelper;
?>
```

Une pincée de style, peut-être :

```html
<style>
    .livelog-panel {
        line-height: normal;
        font-family: monospace;
        font-size: 9px;
        
        border: 2px grey dotted;
        
        height: 300px;
        overflow-y: scroll;
        overflow-x: scroll;
        white-space: nowrap;
        
        padding: 0.5em;
        margin-top: 1em;
    }
</style>
```

Dans un navigateur web, lorsque vous vous rendez sur la page correspondant à `voirAction`, vous devriez voir une
div contenant un message du genre `:-) Connexion WebSocket OK` si la connexion à la websocket a pu être établie
(ou `:-( Connexion WebSocket KO` dans le cas contraire).

Lorsque vous cliquez sur le bouton "Lancer" (ou équivalent), des lignes de logs devraient
s'afficher les unes sous les autres au fur et à mesure qu'elles sont produites dans `lancerAction`.


NB : Pas de vue `lancer.phtml` associée à `lancerAction` puisqu'elle est requêtée en mode AJAX.

composer.json

0 → 100644
+23 −0
Original line number Diff line number Diff line
{
    "name": "unicaen/livelog",
    "description": "Module permettant d'afficher des logs en live à l'aide d'une websocket (et d'une socket)",
    "require": {
        "php": ">=7.3",
        "unicaen/app": "^4.0",
        "webmozart/assert": "^1.3",
        "cboden/ratchet": "^0.4.4",
        "monolog/monolog": "^2.3"
    },
    "require-dev": {
        "laminas/laminas-test": "^3.2",
        "phpunit/phpunit": "^5.6"
    },
    "autoload": {
        "psr-0": {
            "UnicaenLivelog": "src/"
        },
        "classmap": [
            "./Module.php"
        ]
    }
}
Loading