andydefer/laravel-logger 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

andydefer/laravel-logger

Composer 安装命令:

composer require andydefer/laravel-logger

包简介

A structured logging package for Laravel that writes logs in JSONL format (JSON Lines).

README 文档

README

composer require andydefer/laravel-logger

Le package s'enregistre automatiquement via Laravel.

Configuration

Variables d'environnement (optionnel)

LOGGER_PATH=/custom/log/path
LOGGER_RETENTION_DAYS=60

Publication du fichier de config (optionnel)

php artisan vendor:publish --tag=logger-config

Premier log

use AndyDefer\DomainStructures\Utils\StrictDataObject;
use AndyDefer\Logger\Records\LogDataRecord;
use AndyDefer\Logger\Contracts\LoggerInterface;

class UserController extends Controller
{
    public function __construct(
        private readonly LoggerInterface $logger,
    ) {}
    
    public function login()
    {
        $payload = new StrictDataObject([
            'event' => 'user_login',
            'user_id' => 123,
            'ip' => '127.0.0.1',
            'success' => true,
        ]);

        $logData = new LogDataRecord(type: 'auth', payload: $payload);

        $this->logger->info($logData);
    }
}

Résultat dans le fichier de log :

{"time":"2026-04-05T10:26:00Z","level":"info","data":{"type":"auth","payload":{"event":"user_login","user_id":123,"ip":"127.0.0.1","success":true}}}

⚠️ Le payload utilise StrictDataObject qui préserve exactement les noms de clés (camelCase ou snake_case). Le timestamp est automatique.

Les 4 niveaux de log

$logger->debug($logData);   // DEBUG
$logger->info($logData);    // INFO
$logger->warning($logData); // WARNING
$logger->error($logData);   // ERROR

Types de payload

StrictDataObject accepte n'importe quelle structure clé-valeur :

Type Exemple
int 'user_id' => 123
float 'amount' => 99.99
string 'ip' => '127.0.0.1'
bool 'success' => true
null 'optional' => null
array 'tags' => ['premium', 'vip']
AbstractRecord 'user' => $userRecord
TypedCollection 'items' => $collection

Travailler avec le payload

Lire des propriétés

$userId = $log->data->payload->user_id;      // Accès direct
$ip = $log->data->payload->ip;               // via propriété
$value = $log->data->payload->get('key');    // avec valeur par défaut
$hasKey = $log->data->payload->has('key');   // Vérifier existence

Convertir en tableau

$array = $log->data->payload->toArray();
// ['event' => 'user_login', 'user_id' => 123, ...]

Immuabilité - Créer une nouvelle version

$newPayload = $payload->with('status', 'completed');  // Ajoute/modifie
$merged = $payload->merge(['new_key' => 'value']);    // Fusionne
$reduced = $payload->without('temp_key');              // Supprime

Requêter les logs

Query par type d'événement

use AndyDefer\Logger\Records\LogQueryRecord;
use AndyDefer\Logger\ValueObjects\IsoZuluTime;

$query = new LogQueryRecord(
    from: new IsoZuluTime('2026-04-05T00:00:00Z'),
    to: new IsoZuluTime('2026-04-05T23:59:59Z'),
    type: 'user_login',
);

$results = $logger->query($query);

Query par niveau

use AndyDefer\Logger\Enums\LogLevel;

$query = new LogQueryRecord(
    from: new IsoZuluTime('2026-04-01T00:00:00Z'),
    to: new IsoZuluTime('2026-04-30T23:59:59Z'),
    level: LogLevel::ERROR,
);

$errors = $logger->query($query);

Query combinée

$from = new IsoZuluTime(now()->subDay()->toIso8601ZuluString());

$query = new LogQueryRecord(
    from: $from,
    to: new IsoZuluTime(now()->toIso8601ZuluString()),
    type: 'payment_failed',
    level: LogLevel::ERROR,
);

$failedPayments = $logger->query($query);

Parcourir les résultats

foreach ($results as $log) {
    echo $log->time->getValue() . "\n";
    echo $log->level->value . "\n";
    echo $log->data->type . "\n";
    echo $log->data->payload->user_id . "\n";
}

Streaming (tous les logs d'un jour)

// Jour spécifique
$logs = $logger->stream('2026-04-05');

// Aujourd'hui
$logs = $logger->stream();

foreach ($logs as $log) {
    // Traitement...
}

Buffer d'écriture (performance)

Le buffer regroupe les logs en mémoire avant de les écrire sur le disque.

Activer le buffer

$logger->enableBuffer(100);  // 100 logs avant écriture automatique

Utilisation

$logger->enableBuffer(50);

// Ces logs restent en mémoire
for ($i = 0; $i < 50; $i++) {
    $logger->info($logData);
}

// Déclenche l'écriture automatique
$logger->info($logData);

// Ou vider manuellement
$logger->flush();

Désactiver

$logger->disableBuffer();  // Vide automatiquement le buffer

Callback à chaque flush

$logger->enableBuffer(100);
$logger->onFlush(function ($count) {
    \Log::info("{$count} logs écrits");
});

Exemples concrets

Authentification

// Connexion réussie
$payload = new StrictDataObject([
    'event' => 'user_login',
    'user_id' => $user->id,
    'ip' => request()->ip(),
    'success' => true,
]);

$logger->info(new LogDataRecord(type: 'auth', payload: $payload));

// Échec de connexion
$payload = new StrictDataObject([
    'event' => 'user_login_failed',
    'email' => request()->email,
    'ip' => request()->ip(),
    'reason' => 'invalid_password',
]);

$logger->warning(new LogDataRecord(type: 'auth', payload: $payload));

Paiement

// Paiement réussi
$payload = new StrictDataObject([
    'event' => 'payment_success',
    'order_id' => $order->id,
    'stripe_id' => $stripeId,
    'amount' => $order->total,
]);

$logger->info(new LogDataRecord(type: 'payment', payload: $payload));

// Paiement échoué
$payload = new StrictDataObject([
    'event' => 'payment_failed',
    'order_id' => $order->id,
    'error' => $exception->getMessage(),
]);

$logger->error(new LogDataRecord(type: 'payment', payload: $payload));

Log avec un Record personnalisé

use AndyDefer\DomainStructures\Abstracts\AbstractRecord;

final class UserRecord extends AbstractRecord
{
    public function __construct(
        public readonly int $id,
        public readonly string $email,
        public readonly string $role,
    ) {}
}

$userRecord = new UserRecord(id: 1, email: 'john@example.com', role: 'admin');

$payload = new StrictDataObject([
    'event' => 'user_created',
    'user' => $userRecord,
]);

$logger->info(new LogDataRecord(type: 'user', payload: $payload));

Tests unitaires

Mock du Logger

use AndyDefer\Logger\Contracts\LoggerInterface;
use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations;

#[AllowMockObjectsWithoutExpectations]
class UserServiceTest extends TestCase
{
    public function test_login_logs_success(): void
    {
        $logger = $this->createMock(LoggerInterface::class);
        
        $logger->expects($this->once())
            ->method('info')
            ->with($this->callback(function ($logData) {
                return $logData->type === 'auth'
                    && $logData->payload->user_id === 123;
            }));
        
        $service = new UserService($logger);
        $service->login(123);
    }
}

Tester la structure, pas le texte

// ✅ BON - Test robuste
$logger->expects($this->once())
    ->method('info')
    ->with($this->callback(fn($log) => $log->payload->user_id === 123));

// ❌ MAUVAIS - Fragile
$logger->expects($this->once())
    ->method('info')
    ->with('User 123 logged in');

LogLevel - méthodes utilitaires

use AndyDefer\Logger\Enums\LogLevel;

$level = LogLevel::INFO;

$level->getLabel();   // 'Info'
$level->isDebug();    // false
$level->isInfo();     // true
$level->isWarning();  // false
$level->isError();    // false

// Toutes les valeurs
LogLevel::values();   // ['debug', 'info', 'warning', 'error']

// Depuis une valeur
LogLevel::fromValue('info'); // LogLevel::INFO

Bonnes pratiques

1. Première propriété = type d'événement

// ✅
$payload = new StrictDataObject([
    'event' => 'user_login',
    'user_id' => $userId,
    'ip' => $ip,
]);

// ❌
$payload = new StrictDataObject([
    'user_id' => $userId,
    'event' => 'user_login',
]);

2. snake_case pour les types

// ✅
'type' => 'user_login'
'type' => 'payment_failed'

// ❌
'type' => 'userLogin'

3. Injection uniquement, pas de facade

// ✅ Injection explicite
class MyService
{
    public function __construct(
        private readonly LoggerInterface $logger,
    ) {}
}

// ❌ Éviter les facades
\Log::info(...);

4. Tester la structure

// ✅ Tester la présence des données
$log->payload->user_id === 123

// ❌ Tester du texte
str_contains($log, 'User 123')

Règle d'or

ZÉRO appel statique. TOUTES les dépendances injectées. Le timestamp est automatique. Les tests vérifient la STRUCTURE, pas le TEXTE.

// ✅ Le log parfait
$payload = new StrictDataObject([
    'event' => 'user_login',
    'user_id' => $userId,
    'ip' => $ip,
    'success' => true,
]);

$logger->info(new LogDataRecord(type: 'auth', payload: $payload));
// ✅ Le test parfait
$logger->expects($this->once())
    ->method('info')
    ->with($this->callback(fn($log) => 
        $log->type === 'auth' 
        && $log->payload->user_id === $userId
    ));

Pourquoi ce package ?

Les faiblesses du système de log natif de Laravel

Problème Explication Conséquence
Format non structuré Les logs sont du texte libre Impossible de parser ou filtrer efficacement
Types non préservés Log::info('message', ['user' => $user])"Array" Perte d'information, données inexploitables
Pas de requêtage On ne peut chercher que par texte Impossible de filtrer par type d'événement ou par niveau
Tests fragiles assertStringContainsString('User 123', $log) Un simple changement de texte casse les tests
Format non standard Format propriétaire Laravel Difficile à intégrer avec des outils externes (ELK, Loki, Datadog)

Les avantages de ce package

Avantage Explication
Format JSONL standard Chaque ligne est un JSON valide, compatible avec tous les outils
Types préservés Les entiers, booléens, objets restent typés
Requêtage puissant Filtrage par type, niveau, plage de dates avec IsoZuluTime
Tests robustes On teste la structure ($log->payload->user_id), pas le texte
Séparation claire type = événement, payload = données
Performance Buffer d'écriture, organisation par heure
Maintenance automatique Nettoyage des vieux logs configurable
Directive intégrée Nettoyage via CLI sans dépendre d'Artisan

Exemple comparatif

// ❌ Laravel natif - Perte d'information
Log::info("Utilisateur {$user->id} connecté", ['ip' => $ip]);
// Sortie: [2024-01-15 14:30:00] local.INFO: Utilisateur 123 connecté {"ip":"127.0.0.1"}

// ✅ Ce package - Structure complète
$payload = new StrictDataObject([
    'event' => 'user_login',
    'user_id' => $user->id,
    'ip' => $ip,
    'success' => true,
]);

$logger->info(new LogDataRecord(type: 'auth', payload: $payload));
// Sortie: {"time":"2024-01-15T14:30:00Z","level":"info","data":{"type":"auth","payload":{"event":"user_login","user_id":123,"ip":"127.0.0.1","success":true}}}

Licence

MIT © Andy Defer

---

andydefer/laravel-logger 适用场景与选型建议

andydefer/laravel-logger 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 153 次下载、GitHub Stars 达 0, 最近一次更新时间为 2026 年 05 月 22 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 andydefer/laravel-logger 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 andydefer/laravel-logger 我们能提供哪些服务?
定制开发 / 二次开发

基于 andydefer/laravel-logger 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 153
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 43
  • 依赖项目数: 2
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-05-22