Activity Logging In TYPO3 Flow

    Introduction

    An activity log is important for analyzing the user activities that occur on a website. It provides statistics which prove to be useful in tracking the performance of a website.

    These are the records which are maintained, in order to keep track of visitors to a website. The errors that occur in site functionality are also recorded. Measuring the number of visitors to the website is the log's most important function. The details that are recorded may include the following:

    1. Number of page views

    2. Visiting time and date

    3. Number of downloaded files

    4. Referral source of the visitor

    Doing activity logging in TYPO3 Flow is very simple using Aspect Oriented Programming(AOP) and if needed you can develop annotation for it.

    How to do Activity Logging?

      You can log user activities either in file or in database.

      To log in file you can just inject SystemLoggerInterface and write an aspect to log the message in a log file.

      To log in to the database you have to:

      1. Develop an annotation to add in action to log

      2. Create one domain model say ActivityLog.php and its repository.

      Develop an annotation to add in action to log
      To develop new annotation:
      1. Create a folder with name Annotations inside Lelesys/App/TestPackage/Classes/
      2. Create a php class with name Log.php

      <?php

          namespace Lelesys\App\TestPackage\Annotations;

          /* *                                                               *
           * This script belongs to the package "Lelesys.App.TestPackage".   *
           *                                                                 *
           *                                                                 */

          use Doctrine\Common\Annotations\Annotation as DoctrineAnnotation;

          /**
           * Methods annotated with this annotation will be logged in the Application Logger
           * after the annotated methods are called.
           *
           * @Annotation
           * @DoctrineAnnotation\Target("PROPERTY")
           */
           final class Log {
          
              /**
               * The additional information
               * @var string
               */
              public $activity;

              /**
               * @param array $values
               * @throws \InvalidArgumentException
               */
              public function __construct($values) {
                  if (isset($values['activity'])) {
                      $this->activity = $values['activity'];
                  }
              }

          }
      ?>

      Create one domain model say ActivityLog.php and its repository.

      ActivityLog.php

      <?php
          namespace Lelesys\App\TestPackage\Domain\Model;

          use Neos\Flow\Annotations as Flow;
          use Doctrine\ORM\Mapping as ORM;

          /**
           * @Flow\Entity
           */
          class ActivityLog {
              /**
               * The user
               * @var \Lelesys\App\TestPackage\Domain\Model\User
               * @ORM\ManyToOne
               */
              protected $user;

              /**
               * The activity time of user
               * @var \DateTime
               */
              protected $activityTime;

              /**
               * The activty done by user
               * @var string
               */
              protected $activity;

              /**
               * The user Ip address
               * @var string
               */
              protected $userIp;

              public function __construct() {
                  $this->activityTime = new \DateTime();
              }

              /**
               * Gets the user
               *
               * @return \Lelesys\App\TestPackage\Domain\Model\User
               */
              public function getUser() {
                  return $this->user;
              }

              /**
               * Sets the user
               *
               * @param \Lelesys\App\TestPackage\Domain\Model\User $user
               * @return void
               */
              public function setUser($user) {
                  $this->user = $user;
              }

              /**
               * Gets activity time
               *
               * @return \DateTime
               */
              public function getActivityTime() {
                  return $this->activityTime;
              }

              /**
               * Sets activity time
               *
               * @param \DateTime $activityTime
               * @return void
               */
              public function setActivityTime($activityTime) {
                  $this->activityTime = $activityTime;
              }

              /**
               * Gets activity
               *
               * @return string
               */
              public function getActivity() {
                  return $this->activity;
              }

              /**
               * Sets activity
               *
               * @param string $activity
               * @return void
               */
              public function setActivity($activity) {
                  $this->activity = $activity;
              }

              /**
               * Gets user ip address
               *
               * @return string
               */
              public function getUserIp() {
                  return $this->userIp;
              }

              /**
               * Sets user ip address
               *
               * @param string $userIp
               * @return void
               */
              public function setUserIp($userIp) {
                  $this->userIp = $userIp;
              }
      }
      ?>

      After creation domain model do following commands :
      ./flow doctrine:migrationgenerate
      ./flow doctrine:migrate
      After doing this a new table will be created in database and all the data will get added in this table.

      AOP for logging data into database

      1. Create a folder with name Aop inside Lelesys/App/TestPackage/Classes/
      2. Create a php class with name LoggingAspect.php
      3. Create a Folder with name Logger inside Lelesys/App/TestPackage/Classes/
      4. Create an interface ApplicationLoggerInterface which extends \Neos\Flow\Log\LoggerInterface inside Logger
      5. Create a php class ApplicationLogger which extends \Neos\Flow\Log\Logger and implements \Lelesys\App\TestPackage\Logger\ApplicationLoggerInterface inside Logger 


      LoggingAspect.php

       <?php

          namespace Lelesys\App\TestPackage\Aop;

          use Neos\Flow\Annotations as Flow;

          /**
           * @Flow\Aspect
           */
          class LoggingAspect {

              /**
               * @var \Neos\Flow\Object\ObjectManagerInterface
               * @Flow\Inject
               */
              protected $objectManager;

              /**
               * @var \Lelesys\App\TestPackage\Logger\ApplicationLoggerInterface
               * @Flow\Inject
               */
              protected $applicationLogger;

              /**
               * @var \Neos\Flow\Reflection\ReflectionService
               * @Flow\Inject
               */
              protected $reflectionService;

              /**
               * Log a message
               *
               * @param \Neos\Flow\Aop\JoinPointInterface $joinPoint
               * @Flow\After("methodAnnotatedWith(Lelesys\App\TestPackage\Annotations\Log)")
               * @return void
               */
              public function logUserActivity(\Neos\Flow\Aop\JoinPointInterface $joinPoint) {
                  $className = $joinPoint->getClassName();
                  $actionName = $joinPoint->getMethodName();
                  $post = $joinPoint->getMethodArguments();
                  $objectName = $this->objectManager->getObjectNameByClassName($className);
                  $packageKey = $this->objectManager->getPackageKeyByObjectName($objectName);
                  $logObj = $this->reflectionService->getMethodAnnotation($className, $joinPoint->getMethodName(), 'Lelesys\App\TestPackage\Annotations\Log');
                  $this->applicationLogger->log($logObj->activity , $packageKey, $className, $joinPoint->getMethodName());
              }

          }
      ?>

      Because of @Flow\After("methodAnnotatedWith(Lelesys\App\TestPackage\Annotations\Log)") this line this aspect is called after all methods which are annoted with @\Lelesys\App\TestPackage\Annotations\Log(activity="visited index action")

      ApplicationLoggerInterface.php 

      <?php

          namespace Lelesys\App\TestPackage\Logger;

          interface ApplicationLoggerInterface extends \Neos\Flow\Log\LoggerInterface {
          
          }
      ?>


      ApplicationLogger.php

      <?php

          namespace Lelesys\App\TestPackage\Logger;

          use Neos\Flow\Annotations as Flow;

          class ApplicationLogger extends \Neos\Flow\Log\Logger implements \Lelesys\App\TestPackage\Logger\ApplicationLoggerInterface {

              /**
               * Security context
               *
               * @Flow\Inject
               * @var \Neos\Flow\Security\Context
               */
              protected $securityContext;

              /**
               * Activity log service
               *
               * @var \Lelesys\App\TestPackage\Domain\Repository\ActivityLogRepository
               * @Flow\Inject
               */
              protected $activityLogRepository;

              /**
               * Injection for persistence manager
               *
               * @Flow\Inject
               * @var \Neos\Flow\Persistence\PersistenceManagerInterface
               */
              protected $persistenceManager;

              /**
               * Log user actions in databse
               *
               * @param string $message Message
               * @param string $severity Severity
               * @param array $additionalData Additional data of service
               * @param string $packageKey Package key
               * @param string $className Class name
               * @param string $methodName Method name
               * @return void
               */
              public function log($message, $additionalData = NULL, $packageKey = NULL, $className = NULL, $methodName = NULL, $severity = LOG_INFO) {
                  $activity = new \Lelesys\App\TestPackage\Domain\Model\ActivityLog();
                  if ($this->securityContext->canBeInitialized()) {
                      $user = $this->securityContext->getParty();
                      if ($user instanceof \Neos\Party\Domain\Model\Person) {
                          $activity->setUser($user);
                      }
                  }
                  $activity->setActivity($message);
                  $activity->setActivityTime(new \DateTime());
                  $activity->setUserIp($_SERVER['REMOTE_ADDR']);
                  $this->activityLogRepository->add($activity);
                  $this->persistenceManager->persistAll();
              }

          }

      ?>

      ApplicationLogger class implements “log” function of ApplicationLoggerInterface and in “log” function code to add ActivityLog Object is written. Since in get action it is not possible to do databse operation we have added persistenceManager->persistAll().

      By doing all these things a new record will get created on each action for which you have added @ Lelesys\App\TestPackage\Annotations\Log annotation.


      Categories: Flow Framework
      Tags: Log, TYPO3Flow

      Copyright © 2018 Lelesys Informatik GmbH, Deutschland. All Rights Reserved.