Magento 2. Monolog atau cara menulis log

Mempelajari berbagai modul untuk Magento 2, Anda akan melihat bahwa logging lebih jarang digunakan daripada Magento 1. Ini sebagian besar disebabkan oleh fakta bahwa logging menjadi lebih sulit. Di sini saya ingin berkonsentrasi pada sisi teknis masalah ini, yaitu cara mencatat data, cara menulis log ke file Anda sendiri, dan apa itu Monolog.

Daftar isi


Monolog
Fitur Aplikasi di Magento 2
Implementasi
Logging menggunakan logger standar
Logging menggunakan logger standar dengan saluran kustom
Menulis ke file khusus menggunakan handler Anda sendiri
Menulis ke file khusus menggunakan virtualType
Pencatatan data cepat
Kesimpulan

Monolog


Mari kita mulai dengan pertanyaan paling penting - Apa itu Monolog dan dari mana asalnya.

Monolog - Ini adalah perpustakaan yang mengimplementasikan standar PSR-3 untuk pendataan data. Monolog yang digunakan dalam Magento 2 untuk merekam log.

PSR-3 , pada gilirannya, merupakan standar yang menggambarkan pendekatan umum untuk pencatatan data dan rekomendasi untuk menerapkan penebang yang menyediakan antarmuka umum.

Sorotan PSR-3
1. Logger (objek) harus mengimplementasikan antarmuka \ Psr \ Log \ LoggerInterface.
2. Kami memiliki tingkat kesalahan berikut (ditunjukkan dalam urutan prioritas dari lebih besar ke lebih rendah):
DARURAT - Sistem tidak dapat digunakan.
ALERT - Tindakan harus segera diambil. Contoh: Seluruh situs web mati, basis data tidak tersedia, dll.
KRITIS - Kondisi kritis. Contoh: Komponen aplikasi tidak tersedia, pengecualian tak terduga.
GALAT - Kesalahan runtime yang tidak memerlukan tindakan segera tetapi biasanya harus dipantau.
PERINGATAN - Kejadian luar biasa yang bukan kesalahan. Contoh: Penggunaan API yang tidak digunakan lagi.
PEMBERITAHUAN - Peristiwa normal namun signifikan.
INFO - Acara menarik. Contoh: Pengguna masuk, SQL log.
DEBUG - Informasi debug mendetail.

3. Setiap tingkat memiliki metode sendiri (debug, info, pemberitahuan, peringatan, kesalahan, kritis, waspada, emerg / darurat) dan harus ada metode log, yang menggunakan tingkat kesalahan sebagai parameter pertama.
4. Metode menerima string atau apa pun yang mengimplementasikan __toString () (yaitu, Anda harus menggunakan print_r ($ message, true) secara manual untuk array atau meneruskannya di parameter berikutnya).
5. Semua metode menerima array $ context yang melengkapi log.
6. Dapat, tetapi tidak harus, menjadi substitusi data dari $ context array ke dalam pesan. Dalam hal ini, format {name} direkomendasikan, di mana nama -> kunci array dalam $ context.


Monolog cukup mudah digunakan. Mari kita lihat contoh berikut ini.

use Monolog\Logger; use Monolog\Handler\StreamHandler; use Monolog\Formatter\HtmlFormatter; //     "name" $log = new Logger('name'); //  ,      "path/to/your1.log"       "WARNING"   (notice, info  debug   ). $log->pushHandler(new StreamHandler('path/to/your1.log', Logger::WARNING)); //  ,      "path/to/your2.log"       "ALERT"   (..        alert  emergency).    ,    .         html   HtmlFormatter. $log->pushHandler(new StreamHandler('path/to/your2.log', Logger::ALERT,false) ->setFormatter(new HtmlFormatter)); //  ,       . $log->pushProcessor(function ($record) { $record['extra']['dummy'] = 'Hello world!'; return $record; }); //   $log->warning('Foo'); $log->error('Bar',['test']); $log->info('Test'); //   ,    1     INFO //your1.log // [2019-08-12 02:57:52] name.WARNING: Foo [] ['extra'=>['dummy'=>'Hello world!']] // [2019-08-12 02:57:53] name.ERROR: BAR ['test'] ['extra'=>['dummy'=>'Hello world!']] //your2.log // , .    ALERT  EMERGENCY 

Sorotan pekerjaan Monolog yang perlu diingat:

  • Logger adalah objek yang kami gunakan untuk merekam log. Logger itu sendiri tidak mencatat, tetapi mengelola penangan. Jumlah apa pun dapat dibuat.
  • Handler - objek yang memproses data secara langsung. Anda dapat menambahkan sebanyak mungkin penangan ke logger sesuka Anda. Semuanya akan dipanggil secara bergantian, terlepas dari apakah penangan yang diberikan dapat memproses kesalahan atau tidak. Metode isHandling menentukan apakah penangan ini akan dapat menangani kesalahan yang diterima.

     public function isHandling(array $record) { return $record['level'] >= $this->level; } 

    Kemiripan terdekat dengan pawang, menurut pendapat saya, adalah Pengamat Acara.
  • Prosesor - semua entitas yang dipanggil (dapat dipanggil). Mungkin beberapa. Mereka dapat ditugaskan secara global dan diinstal untuk handler. Pertama, prosesor global diluncurkan. Tugas utama prosesor adalah untuk menambahkan data tambahan ke log (misalnya, IP dari mana koneksi itu, nilai variabel global, informasi di mana cabang git kode terletak, dan sebagainya).
  • Formatter - Mengkonversi output pesan sebelum menulis. Hanya ada 1 per handler. Anda perlu mengubah format badan pesan, misalnya, untuk mengubah teks menjadi html atau json.
  • Channel - nama logger. Itu akan ditulis saat merekam log. Karena 1 handler dapat digunakan dalam 2 logger yang berbeda (ini akan menulis log ke 1 file yang sama), ini akan menentukan dari mana kesalahan itu berasal.
  • Level - level kesalahan. Parameter ini untuk pawang berarti tingkat kesalahan minimum yang akan ditangani.
  • Bubble - popup pesan. Setelah pawang memproses pesan, logger meneruskan pesan ke pawang berikutnya. Proses ini dapat dihentikan menggunakan properti gelembung. Jika pawang memiliki nilai properti ini false (default selalu benar), maka setelah pawang ini melakukan tugasnya (mampu memproses kesalahan ini), pawang lain tidak akan memulai.
  • Sortir Pesanan - urutan eksekusi. Handler yang ditambahkan terakhir selalu diluncurkan paling pertama. Ini adalah fitur ini yang memungkinkan Anda untuk menerapkan mekanisme untuk sepenuhnya menonaktifkan logger (via bubbling false). Penangan yang ditambahkan melalui konstruktor masuk dalam urutan yang ditentukan dalam konstruktor.

Karena PSR-3 tidak mewajibkan pengembang untuk menerapkan nilai AutoCorrect dalam teks, Monolog tidak melakukan ini secara default. Jika Anda menulis -> emerg ('test 1111 {placeholder}', ['placeholder' => 'foo']) Anda akan mendapatkan yang berikut
[2019-08-12 02:57:52] main.EMERGENCY: test 1111 {placeholder} {"placeholder": "foo"} []

Agar penggantian berfungsi, Anda harus menghubungkan prosesor tambahan - \ Monolog \ Processor \ PsrLogMessageProcessor.
Perlu dikatakan bahwa Monolog memiliki banyak Formatter, Processor, Handler di luar kotak. Anda dapat menggunakannya atau menulis sendiri.

Fitur Aplikasi di Magento 2


Di situs web resmi Magento, Anda dapat menemukan contoh umum tentang cara menggunakan logger. Sayangnya, contoh yang disajikan tidak mengungkapkan semua detail dan sayangnya, tidak menjawab pertanyaan "bagaimana menulis log ke file Anda sendiri." Karena itu, mari kita memahami semuanya dengan lebih detail.

Pada zaman Magento 1, mungkin cepat atau lambat semua orang menggunakan metode Mage :: log, yang tersedia di mana-mana dalam kode dan entri log yang paling sederhana tampak seperti Mage :: log ('ALARM!', Null, 'api.log'). Akibatnya, kami memiliki catatan bentuk berikut di file var / log / api.log

 2019-08-12T01:00:27+00:00 DEBUG (7): ALARM! 

Format default:% timestamp %% priorityName% (% priority%):% message%.

Mari kita lihat cara mencatat data dalam kasus paling sederhana di Magento 2. Paling sering Anda akan menggunakan $ this -> _ logger-> info ('ALARM!'); (jika suatu objek memiliki properti seperti itu, misalnya, diwarisi).

Sebagai hasil dari panggilan seperti itu, kami mendapatkan entri berikut di file var / log / system.log

 [2019-08-12 02:56:43] main.INFO: ALARM! [] [] 

Format default adalah [% datetime%]% channel%.% Level_name%:% message %% konteks %% ekstra%
Jika objek tidak memiliki properti seperti itu (_logger atau logger), maka kita harus terlebih dahulu menambahkan dependensi \ Psr \ Log \ LoggerInterface ke kelas Anda dan tulis objek yang dihasilkan ke properti $ logger (sesuai dengan PSR-2 poin 4.2 dan contoh yang disajikan di situs web Magento ) .
Tidak seperti Magento 1, ada lebih banyak nuansa di sini.

1. Parameter untuk logging.

Pertimbangkan panggilan umum ke metode tulis

 $this->_logger->{level}($message, $context = []); //$this->_logger->log('{level}', $message, $context = []); 

1) Di mana {level} berada, menurut PSR-3, 1 metode yang disediakan untuk mencatat level kesalahan tertentu (debug, info, pemberitahuan, peringatan, kesalahan, kritis, waspada, emerg / darurat).
2) $ message - tidak seperti Magento 1, seharusnya berupa string. Yaitu $ object-> getData () tidak akan berfungsi di sini. Array data harus diteruskan ke parameter berikutnya. \ Objek pengecualian adalah pengecualian, karena implementasi \ Magento \ Framework \ Logger \ Monolog memprosesnya secara terpisah dan secara otomatis menggulung -> getMessage () selanjutnya sebagai $ message jika objek \ Exception dilewatkan sebagai pesan.
3) $ context adalah parameter opsional, sebuah array.

2. Properti $ this -> _ logger tidak tersedia di semua kelas.

Hadir dalam: Blokir, Helper, Model, Koleksi, dll.
Tidak tersedia di: ResourceModel, Pengendali, Perintah, Pengaturan, dll.

Pelajari lebih lanjut tentang ResourceModel dan Koleksi.
ResourceModel memiliki properti _logger, tetapi tidak diisi di konstruktor. Ini diisi hanya menggunakan metode getLogger pribadi di \ Magento \ Framework \ Model \ ResourceModel \ AbstractResource. Metode ini dipanggil hanya jika terjadi kesalahan saat menulis ke database (di blok tangkap) di dalam metode commit (). Sampai saat itu, model sumber daya tidak akan memiliki logger.

 public function commit() { $this->getConnection()->commit(); if ($this->getConnection()->getTransactionLevel() === 0) { $callbacks = CallbackPool::get(spl_object_hash($this->getConnection())); try { foreach ($callbacks as $callback) { call_user_func($callback); } } catch (\Exception $e) { $this->getLogger()->critical($e); } } return $this; } … private function getLogger() { if (null === $this->_logger) { $this->_logger = ObjectManager::getInstance()->get(\Psr\Log\LoggerInterface::class); } return $this->_logger; } 

Collection memiliki logger dari awal. Ini ditugaskan di konstruktor \ Magento \ Framework \ Data \ Collection \ AbstractDb dan kemudian diwarisi.

Tidak mungkin untuk tidak mengatakan, tetapi di controller ada cara untuk mendapatkan logger menggunakan ObjectManager (melalui $ this -> _ objectManager properti). Tapi ini tentu saja bukan cara yang paling benar.

3. Pencatat default dan daftar penangan.

Di global di.xml (app / etc / di.xml) Anda dapat menemukan bahwa \ Psr \ Log \ LoggerInterface diimplementasikan oleh kelas \ Magento \ Framework \ Logger \ Monolog, yang pada gilirannya mewarisi dari \ Monolog \ Logger. Nama logger adalah utama. Beberapa penangan juga didefinisikan di sana.

 <preference for="Psr\Log\LoggerInterface" type="Magento\Framework\Logger\Monolog" /> ... <type name="Magento\Framework\Logger\Monolog"> <arguments> <argument name="name" xsi:type="string">main</argument> <argument name="handlers" xsi:type="array"> <item name="system" xsi:type="object">Magento\Framework\Logger\Handler\System</item> <item name="debug" xsi:type="object">Magento\Framework\Logger\Handler\Debug</item> <item name="syslog" xsi:type="object">Magento\Framework\Logger\Handler\Syslog</item> </argument> </arguments> </type> ... 

Beberapa kelas berbeda dari yang terdaftar di atas (karena didefinisikan ulang dalam modul Magento \ Developer):

1) Magento \ Framework \ Logger \ Handler \ System ( mendengarkan INFO)
2) Magento \ Pengembang \ Model \ Logger \ Handler \ Debug ( mendengarkan DEBUG )
3) Magento \ Developer \ Model \ Logger \ Handler \ Syslog ( mendengarkan DEBUG )

Di kelas yang ditentukan (Debug dan Syslog) , kemampuan untuk menonaktifkan logging (dev / debug / debug_logging dan dev / syslog / syslog_logging, masing-masing) ditambahkan.

Perhatikan bahwa tidak ada penangan perkecualian dalam daftar penangan yang menulis ke exception.log. Ini disebut dalam System Handler.

Magento \ Framework \ Logger \ Handler \ System
 ... public function write(array $record) { if (isset($record['context']['exception'])) { $this->exceptionHandler->handle($record); return; } $record['formatted'] = $this->getFormatter()->format($record); parent::write($record); } ... 


Magento 2 hingga 2.2 memiliki masalah dengan penebang tidak dapat melompat ke handler lain setelah yang pertama ditemukan. Masalah ini disebabkan oleh fakta bahwa Monolog menghitung bahwa semua penangan datang kepadanya dalam array dengan kunci digital, dan datang dengan kunci alfabet (['sistem' =>, 'debug' =>, ...]). Pengembang Magento kemudian memperbaiki situasi - mereka mengubah hash menjadi array biasa dengan tombol digital sebelum meneruskannya ke Monolog. Monolog sekarang juga telah mengubah algoritma enumerasi handler dan menggunakan metode next ().
4. Memperkenalkan pawang Anda ke daftar yang sudah ada.

Kami sampai pada hal yang paling menarik, yang merusak kesan implementasi di Magento 2. sedikit. Anda tidak dapat menambahkan custom handler ke daftar yang ada menggunakan di.xml tanpa ... "gesture tambahan". Ini disebabkan oleh prinsip konfigurasi gabungan.

Ada beberapa Ruang Lingkup Config :

1) Awal (aplikasi / etc / di.xml)
2) Global ({moduleDir} /etc/di.xml)
3) Khusus area (mis. {ModuleDir} / etc / {area} /di.xml. Frontend / adminhtml / crontab / webapi_soap / webapi_rest, dll.)

Di dalam level 1, konfigurasi digabung, tetapi level berikutnya mendefinisikannya di penggabungan (jika mereka juga dideklarasikan di sana). Ini membuat tidak mungkin untuk menambahkan penangan dalam modul mereka ke daftar yang ada, karena dideklarasikan dalam ruang lingkup awal.

Mungkin di masa depan kita akan melihat implementasi di mana penambahan penangan akan dipindahkan dari ruang lingkup awal ke beberapa modul lain, dengan demikian dipindahkan ke ruang lingkup global.

Implementasi


Mari kita lihat cara utama mencatat log, yang dapat bermanfaat bagi kita dalam pelaksanaan tugas.

1. Logging menggunakan logger standar


Metode ini memungkinkan kita untuk dengan mudah menulis log di 1 dari log standar (debug.log, system.log atau exception.log).

 class RandomClass { private $logger; public function __construct(\Psr\Log\LoggerInterface $logger) { $this->logger = $logger; } public function foo() { $this->logger->info('Something went wrong'); //[...some date...] main.INFO: Something went wrong [] [] } } 

Semuanya menjadi lebih sederhana jika sudah ada ketergantungan logger bawaan di kelas kami.

 $this->_logger->info('Something went wrong'); //    ->debug,   ,      debug.log ... 

2. Logging menggunakan logger standar dengan saluran kustom


Metode ini berbeda dari yang sebelumnya di mana tiruan dari logger dibuat dan saluran lain (nama) ditugaskan untuk itu. Yang akan menyederhanakan pencarian di dalam file log.

 class RandomClass { private $logger; public function __construct(\Psr\Log\LoggerInterface $logger) { $this->logger = $logger->withName('api'); //    } public function foo() { $this->logger->info('Something went wrong'); //[...some date...] api.INFO: Something went wrong [] [] } } 


Untuk mencari log yang diperlukan, sekarang cukup menggunakan pencarian dengan "api" (logger default di Magento 2 disebut main) di file system.log, debug.log, exception.log yang ada. Bisa digunakan
 grep -rn 'api' var/log/system.log 


3. Menulis ke file khusus menggunakan handler Anda sendiri


Mari kita membuat penangan sederhana yang mencatat semua kesalahan level Kritis dan lebih tinggi ke file terpisah var / log / critical.log. Tambahkan kemampuan untuk memblokir semua penangan lain untuk tingkat kesalahan yang diberikan dan lebih tinggi. Ini akan menghindari duplikasi data dalam file debug.log dan system.log.

 <?php namespace Oxis\Log\Logger\Handler; use Magento\Framework\Filesystem\DriverInterface; use Magento\Framework\Logger\Handler\Base; use Monolog\Logger; class Test extends Base { protected $fileName = 'var/log/critical.log'; protected $loggerType = Logger::CRITICAL; public function __construct(DriverInterface $filesystem) { parent::__construct($filesystem,null,null); $this->bubble = false; //      setBubble() } } 

Di Magento 2 2.2+ di konstruktor \ Magento \ Framework \ Logger \ Handler \ Basis cara memproses path ke file log telah berubah
 // BP . $this->fileName // BP. DIRECTORY_SEPARATOR . $this->fileName 

Oleh karena itu, di penangan lama Anda masih dapat menemukan / di awal $ fileName.


Sebagai contoh, sedikit penjelasan layak untuk diberikan. Karena Basis tidak memungkinkan Anda untuk mengatur properti gelembung melalui parameter konstruktor, kami harus mengulang bagian dari kode dari konstruktor Basis untuk meneruskan parameter input dengan benar ke induk dari kelas Basis (yang, omong-omong, memiliki parameter input untuk mengatur properti ini) atau menggunakan pendekatan seperti itu. Saya memilih opsi kedua.

 use Oxis\Log\Logger\Handler\Test; use Psr\Log\LoggerInterface; class RandomClass { private $logger; public function __construct( LoggerInterface $logger, Test $handler ) { $logger->pushHandler($handler); //  setHandlers([$handler]),        . $this->logger = $logger; } public function foo() { $this->logger->critical('Something went wrong'); //      critical.log //[...some date...] main.CRITICAL: Something went wrong [] [] } } 

Metode menambahkan handler ini tidak ideal, tetapi memungkinkan Anda untuk keluar dari Lingkup Konfig masalah, yang mengharuskan kami untuk menduplikasi semua logger di di.xml kami. Jika tujuannya adalah untuk mengganti semua penebang dengan milik Anda, maka jauh lebih baik menggunakan pendekatan virtualType, yang akan kami pertimbangkan lebih lanjut.

4. Menulis ke file khusus menggunakan virtualType


Pendekatan ini memungkinkan kita untuk memaksa kelas yang kita butuhkan untuk menulis log ke file log yang ditentukan untuk ini menggunakan di.xml. Anda dapat menemukan pendekatan serupa dalam modul Magento \ Pembayaran dan Magento \ Pengiriman. Saya menarik perhatian Anda pada fakta bahwa pendekatan ini bekerja dimulai dengan Magento 2 2.2 dan lebih tinggi.
Di Magento 2 2.2+, parameter baru ditambahkan ke konstruktor \ Magento \ Framework \ Logger \ Handler \ Base, yang memungkinkan Anda untuk membuat penangan virtual dan menentukan melalui di.xml jalur relatif ke file untuk menulis log. Sebelumnya, diperlukan untuk menentukan path lengkap melalui $ filePath, atau untuk membuat handler baru dan menulis path relatif ke properti file $ fileName yang dilindungi.

Di di.xml modul kami, tambahkan berikut ini
  <!--      ,     --> <virtualType name="ApiHandler" type="Magento\Framework\Logger\Handler\Base"> <arguments> <argument name="fileName" xsi:type="string">var/log/api.log</argument> </arguments> </virtualType> <!--  ,     --> <virtualType name="ApiLogger" type="Magento\Framework\Logger\Monolog"> <arguments> <argument name="name" xsi:type="string">api</argument> <argument name="handlers" xsi:type="array"> <item name="default" xsi:type="object">ApiHandler</item> </argument> </arguments> </virtualType> <!--       --> <type name="Oxis\Log\Model\A"> <arguments> <argument name="logger" xsi:type="object">ApiLogger</argument> </arguments> </type> 

Tambahkan kelas logger ke Oxis \ Log \ Model \ A.
 namespace Oxis\Log\Model; class A { private $logger; public function __construct(\Psr\Log\LoggerInterface $logger) { $this->logger = $logger; } public function foo() { $this->logger->info('Something went wrong'); } } 

Sekarang benar-benar semua log yang akan ditulis di kelas kita akan diproses oleh versi logger kita, yang akan menulis log menggunakan handler kita ke file var / log / api.log.

4.1. Jika kelas menerima logger melalui objek $ context, dan bukan melalui konstruktornya.
Ini termasuk \ Magento \ Katalog \ Model \ Produk, dependensi yang tidak memiliki \ Psr \ Log \ LoggerInterface, tetapi ada \ Magento \ Framework \ Model \ Konteks di mana logger diatur ke properti kelas. Dalam hal ini, kita perlu mempersulit opsi di atas sedikit dan mengganti logger yang terletak di objek $ context. Dan agar ini tidak mempengaruhi keseluruhan Magento, kami akan mengganti $ context hanya untuk kelas kami dengan virtualType.
 <virtualType name="ApiHandler" type="Magento\Framework\Logger\Handler\Base"> <arguments> <argument name="fileName" xsi:type="string">var/log/api.log</argument> </arguments> </virtualType> <virtualType name="ApiLogger" type="Magento\Framework\Logger\Monolog"> <arguments> <argument name="name" xsi:type="string">api</argument> <argument name="handlers" xsi:type="array"> <item name="default" xsi:type="object">ApiHandler</item> </argument> </arguments> </virtualType> <!--       logger--> <virtualType name="ApiLogContainingContext" type="Magento\Framework\Model\Context"> <arguments> <argument name="logger" xsi:type="object">ApiLogger</argument> </arguments> </virtualType> <!--    --> <type name="Oxis\Log\Model\A"> <arguments> <argument name="context" xsi:type="object">ApiLogContainingContext</argument> </arguments> </type> 


5. Pencatatan data cepat


Ada saatnya kita perlu menambahkan logging dengan cepat. Paling sering, ini mungkin diperlukan baik di server produksi atau untuk pengujian cepat.
 ... $log = new \Monolog\Logger('custom', [new \Monolog\Handler\StreamHandler(BP.'/var/log/custom.log')]); $log->error('test'); ... 

Kelebihan dari pendekatan ini: menulis tanggal, ada konteks (array), secara otomatis menambahkan \ n sampai akhir

Dalam contoh di atas, \ Monolog \ Logger diterapkan secara khusus, dan bukan \ Magento \ Framework \ Logger \ Monolog yang meluas. Faktanya adalah bahwa dengan penggunaan ini tidak ada perbedaan, tetapi menulis lebih sedikit (dan mengingatnya lebih mudah).

\ Monolog \ Handler \ StreamHandler, pada gilirannya, digunakan sebagai pengganti \ Magento \ Framework \ Logger \ Handler \ Base karena menggunakan Base sebagai snippet sangat tidak nyaman karena ketergantungan tambahan pada kelas pihak ketiga.

Pendekatan lain yang tidak bisa dikatakan tentang file_put_contents lama yang baik.

 ... file_put_contents(BP.'/var/log/custom.log', 'test',FILE_APPEND); ... 

Keuntungan dari pendekatan ini: menulis relatif cepat dan tidak perlu mengingat kelas.

Dalam kedua kasus, peran utama dimainkan oleh BP konstan. Dia selalu menunjuk ke folder dengan magenta (1 tingkat lebih tinggi dari pub), yang nyaman dan selalu membantu kita menulis log ke tempat yang tepat.

Kesimpulan


Saya harap informasi di atas bermanfaat bagi Anda.

Source: https://habr.com/ru/post/id450636/


All Articles