Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@
"symfony/yaml": "^7",
"symfony/cache": "^7",
"symfony/console": "^7",
"symfony/dotenv": "^7"
"symfony/dotenv": "^7",
"symfony/twig-bundle": "^7",
"symfony/http-foundation": "^7",
"symfony/routing": "^7",
"twig/twig": "^3"
},
"require-dev": {
"lendable/composer-license-checker": "^1.2",
Expand Down
24 changes: 24 additions & 0 deletions config/xml/Bitrix24.Lib.Journal.Entity.JournalItem.dcm.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xs="https://www.w3.org/2001/XMLSchema"
xmlns:orm="https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Bitrix24\Lib\Journal\Entity\JournalItem" table="journal">
<id name="id" type="uuid" column="id">

</id>

<field name="applicationInstallationId" type="uuid" column="application_installation_id" nullable="false"/>

<field name="createdAt" type="carbon_immutable" column="created_at_utc" precision="3" nullable="false"/>

<field name="level" enum-type="string" column="level" nullable="false"/>

<field name="message" type="text" column="message" nullable="false"/>

<embedded name="context" class="Bitrix24\Lib\Journal\ValueObjects\JournalContext"/>

<indexes>
<index name="idx_journal_composite" columns="application_installation_id,level,created_at_utc"/>
<index name="idx_journal_created_at" columns="created_at_utc"/>
</indexes>
</entity>
</doctrine-mapping>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xs="https://www.w3.org/2001/XMLSchema"
xmlns:orm="https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<embeddable name="Bitrix24\Lib\Journal\ValueObjects\JournalContext">
<field name="label" type="string" column="label" nullable="false"/>

<field name="payload" type="json" column="payload" nullable="true"/>

<field name="bitrix24UserId" type="integer" column="bitrix24_user_id" nullable="true"/>

<field name="ipAddress" type="darsyn_ip" column="ip_address" nullable="true"/>
</embeddable>
</doctrine-mapping>
94 changes: 94 additions & 0 deletions src/Journal/Controller/JournalAdminController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

/**
* This file is part of the bitrix24-php-lib package.
*
* © Maksim Mesilov <mesilov.maxim@gmail.com>
*
* For the full copyright and license information, please view the MIT-LICENSE.txt
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Bitrix24\Lib\Journal\Controller;

use Bitrix24\Lib\Journal\Entity\LogLevel;
use Bitrix24\Lib\Journal\ReadModel\JournalItemReadRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Uid\Uuid;

/**
* Admin controller for journal management
* Developer should configure routes in their application
*/
class JournalAdminController extends AbstractController
{
public function __construct(
private readonly JournalItemReadRepository $journalReadRepository
) {
}

/**
* List journal items with filters and pagination
*/
public function list(Request $request): Response
{
$page = max(1, $request->query->getInt('page', 1));
$domainUrl = $request->query->get('domain');
$levelValue = $request->query->get('level');
$label = $request->query->get('label');

$level = null;
if ($levelValue && in_array($levelValue, array_column(LogLevel::cases(), 'value'), true)) {
$level = LogLevel::from($levelValue);
}

$pagination = $this->journalReadRepository->findWithFilters(
domainUrl: $domainUrl ?: null,
level: $level,
label: $label ?: null,
page: $page,
limit: 50
);

$availableDomains = $this->journalReadRepository->getAvailableDomains();
$availableLabels = $this->journalReadRepository->getAvailableLabels();

return $this->render('@Journal/admin/list.html.twig', [
'pagination' => $pagination,
'currentFilters' => [
'domain' => $domainUrl,
'level' => $levelValue,
'label' => $label,
],
'availableDomains' => $availableDomains,
'availableLabels' => $availableLabels,
'logLevels' => LogLevel::cases(),
]);
}

/**
* Show journal item details
*/
public function show(string $id): Response
{
try {
$uuid = Uuid::fromString($id);
} catch (\InvalidArgumentException) {
throw $this->createNotFoundException('Invalid journal item ID');
}

$journalItem = $this->journalReadRepository->findById($uuid);

if (!$journalItem) {
throw $this->createNotFoundException('Journal item not found');
}

return $this->render('@Journal/admin/show.html.twig', [
'item' => $journalItem,
]);
}
}
227 changes: 227 additions & 0 deletions src/Journal/Docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
# Journal Module

PSR-3 совместимый модуль для ведения технологического журнала приложения.

## Компоненты

### 1. PSR-3 Logger Service

**JournalLogger** - реализация `Psr\Log\LoggerInterface` для записи событий в журнал.

#### Использование через фабрику:

```php
use Bitrix24\Lib\Journal\Services\JournalLoggerFactory;
use Symfony\Component\Uid\Uuid;

// Получаем фабрику из DI контейнера
/** @var JournalLoggerFactory $factory */
$factory = $container->get(JournalLoggerFactory::class);

// Создаем логгер для конкретной установки приложения
$installationId = Uuid::fromString('...');
$logger = $factory->createLogger($installationId);

// Используем как обычный PSR-3 логгер
$logger->info('Синхронизация завершена', [
'label' => 'b24.exchange.realtime',
'payload' => [
'action' => 'sync',
'items' => 150,
'duration' => '2.5s'
],
'bitrix24UserId' => 123,
'ipAddress' => '192.168.1.1'
]);

$logger->error('Ошибка обращения к API', [
'label' => 'b24.api.error',
'payload' => [
'method' => 'crm.deal.list',
'error' => 'QUERY_LIMIT_EXCEEDED'
]
]);
```

#### Прямое использование:

```php
use Bitrix24\Lib\Journal\Services\JournalLogger;
use Bitrix24\Lib\Journal\Infrastructure\Doctrine\DoctrineDbalJournalItemRepository;

$logger = new JournalLogger(
applicationInstallationId: $installationId,
repository: $repository,
entityManager: $entityManager
);

// Все PSR-3 методы доступны
$logger->emergency('Критическая ошибка системы');
$logger->alert('Требуется немедленное внимание');
$logger->critical('Критическое состояние');
$logger->error('Ошибка выполнения');
$logger->warning('Предупреждение');
$logger->notice('Важное уведомление');
$logger->info('Информационное сообщение');
$logger->debug('Отладочная информация');
```

### 2. Entities

**JournalItem** - основная сущность журнала с PSR-3 фабричными методами:

```php
use Bitrix24\Lib\Journal\Entity\JournalItem;

// Создание через статические методы
$item = JournalItem::info($installationId, 'Сообщение', [
'label' => 'custom.label',
'payload' => ['key' => 'value']
]);

// Или через create с явным указанием уровня
$item = JournalItem::create(
applicationInstallationId: $installationId,
level: LogLevel::error,
message: 'Сообщение об ошибке',
context: $context
);
```

### 3. Repositories

#### Doctrine Repository (для продакшена)

```php
use Bitrix24\Lib\Journal\Infrastructure\Doctrine\DoctrineDbalJournalItemRepository;

$repository = new DoctrineDbalJournalItemRepository($entityManager);

// Сохранение
$repository->save($journalItem);
$entityManager->flush();

// Поиск
$item = $repository->findById($uuid);
$items = $repository->findByApplicationInstallationId($installationId, LogLevel::error, 50, 0);

// Очистка
$deleted = $repository->deleteByApplicationInstallationId($installationId);
$deleted = $repository->deleteOlderThan(new CarbonImmutable('-30 days'));
```

#### In-Memory Repository (для тестов)

```php
use Bitrix24\Lib\Journal\Infrastructure\InMemory\InMemoryJournalItemRepository;

$repository = new InMemoryJournalItemRepository();

// Тот же интерфейс, что и Doctrine репозиторий
$repository->save($item);
$items = $repository->findAll();
$repository->clear();
```

### 4. Admin UI (ReadModel)

```php
use Bitrix24\Lib\Journal\ReadModel\JournalItemReadRepository;

$readRepo = new JournalItemReadRepository($entityManager, $paginator);

// Получение с фильтрами и пагинацией
$pagination = $readRepo->findWithFilters(
domainUrl: 'example.bitrix24.ru',
level: LogLevel::error,
label: 'b24.api.error',
page: 1,
limit: 50
);

// Получение списков для фильтров
$domains = $readRepo->getAvailableDomains();
$labels = $readRepo->getAvailableLabels();
```

## Структура Context

Context записи может содержать:

- **label** (string|null) - метка для группировки событий (например, 'b24.exchange.realtime')
- **payload** (array|null) - произвольные данные в формате JSON
- **bitrix24UserId** (int|null) - ID пользователя Bitrix24
- **ipAddress** (string|null) - IP адрес (будет сохранен через darsyn/ip library)

## PSR-3 Log Levels

Модуль поддерживает все 8 уровней PSR-3:

1. **emergency** - Система неработоспособна
2. **alert** - Требуется немедленное вмешательство
3. **critical** - Критические условия
4. **error** - Ошибки выполнения
5. **warning** - Предупреждения
6. **notice** - Нормальные, но значимые события
7. **info** - Информационные сообщения
8. **debug** - Детальная отладочная информация

## Testing

Для тестов используйте InMemoryJournalItemRepository:

```php
use Bitrix24\Lib\Journal\Infrastructure\InMemory\InMemoryJournalItemRepository;
use Bitrix24\Lib\Journal\Services\JournalLogger;

class MyTest extends TestCase
{
private InMemoryJournalItemRepository $repository;
private JournalLogger $logger;

protected function setUp(): void
{
$this->repository = new InMemoryJournalItemRepository();
$entityManager = $this->createMock(EntityManagerInterface::class);

$this->logger = new JournalLogger(
Uuid::v7(),
$this->repository,
$entityManager
);
}

public function testLogging(): void
{
$this->logger->info('Test message');

$items = $this->repository->findAll();
$this->assertCount(1, $items);
$this->assertEquals('Test message', $items[0]->getMessage());
}
}
```

## Admin Interface

Модуль включает готовый контроллер и Twig-шаблоны для просмотра журнала:

- `/admin/journal` - список с фильтрами (домен, уровень, метка) и пагинацией
- `/admin/journal/{id}` - детальный просмотр с визуализацией JSON payload

См. `src/Journal/Controller/JournalAdminController.php` и `templates/journal/`.

## Database Schema

Таблица `journal_item` с полями:
- `id` (UUID) - PK
- `application_installation_id` (UUID) - FK к установке приложения
- `created_at_utc` (timestamp) - время создания
- `level` (string) - уровень логирования
- `message` (text) - сообщение
- `label`, `payload`, `bitrix24_user_id`, `ip_address` - поля контекста

Индексы:
- `application_installation_id`
- `created_at_utc`
- `level`
Loading
Loading