1616use Monolog \Handler \AbstractHandler ;
1717use Monolog \Logger ;
1818use Symfony \Component \HttpClient \HttpClient ;
19+ use Symfony \Contracts \HttpClient \Exception \ExceptionInterface ;
1920use Symfony \Contracts \HttpClient \HttpClientInterface ;
2021
2122/**
2223 * Push logs directly to Elasticsearch and format them according to Logstash specification.
2324 *
24- * This handler dials directly with the HTTP interface of elasticsearch . This
25- * means it will slow down your application if elasticsearch takes times to
25+ * This handler dials directly with the HTTP interface of Elasticsearch . This
26+ * means it will slow down your application if Elasticsearch takes times to
2627 * answer. Even if all HTTP calls are done asynchronously.
2728 *
2829 * In a development environment, it's fine to keep the default configuration:
29- * For each log, an HTTP request will be made to push the log to Elasticsearch.
30+ * for each log, an HTTP request will be made to push the log to Elasticsearch.
3031 *
31- * But in a production environment, it's highly recommended to wrap this handler
32- * in an handler with buffering capability (like the FingersCrossedHandler, or
33- * BufferHandler) in order to call Elasticsearch only once. For even better
34- * performance and fault tolerance, a proper ELK stack is recommended.
32+ * In a production environment, it's highly recommended to wrap this handler
33+ * in a handler with buffering capabilities (like the FingersCrossedHandler, or
34+ * BufferHandler) in order to call Elasticsearch only once with a bulk push. For
35+ * even better performance and fault tolerance, a proper ELK (https://www.elastic.co/what-is/elk-stack)
36+ * stack is recommended.
3537 *
3638 * @author Grégoire Pineau <[email protected] > 3739 */
@@ -45,7 +47,7 @@ class ElasticsearchLogstashHandler extends AbstractHandler
4547 public function __construct (string $ endpoint = 'http://127.0.0.1:9200 ' , string $ index = 'monolog ' , HttpClientInterface $ client = null , int $ level = Logger::DEBUG , bool $ bubble = true )
4648 {
4749 if (!interface_exists (HttpClientInterface::class)) {
48- throw new \LogicException (sprintf ('The %s handler needs symfony/http- client, please run ` composer require symfony/http-client` . ' , __CLASS__ ));
50+ throw new \LogicException (sprintf ('The %s handler needs an HTTP client. Try running " composer require symfony/http-client" . ' , __CLASS__ ));
4951 }
5052
5153 parent ::__construct ($ level , $ bubble );
@@ -63,20 +65,16 @@ public function handle(array $record): bool
6365
6466 $ this ->sendToElasticsearch ([$ record ]);
6567
66- return false === $ this ->bubble ;
68+ return ! $ this ->bubble ;
6769 }
6870
6971 public function handleBatch (array $ records ): void
7072 {
71- $ records = array_filter ($ records , function (array $ record ) {
72- return $ this ->isHandling ($ record );
73- });
73+ $ records = array_filter ($ records , [$ this , 'isHandling ' ]);
7474
75- if (! $ records ) {
76- return ;
75+ if ($ records ) {
76+ $ this -> sendToElasticsearch ( $ records ) ;
7777 }
78-
79- $ this ->sendToElasticsearch ($ records );
8078 }
8179
8280 protected function getDefaultFormatter (): FormatterInterface
@@ -116,10 +114,36 @@ private function sendToElasticsearch(array $records)
116114
117115 $ this ->responses ->attach ($ response );
118116
119- foreach ($ this ->client ->stream ($ this ->responses , 0 ) as $ response => $ chunk ) {
120- if (!$ chunk ->isTimeout () && $ chunk ->isFirst ()) {
117+ $ this ->wait (0.0 , false );
118+ }
119+
120+ public function __destruct ()
121+ {
122+ $ this ->wait (null , true );
123+ }
124+
125+ private function wait (?float $ timeout , bool $ errorOnTimeout )
126+ {
127+ $ e = null ;
128+
129+ foreach ($ this ->client ->stream ($ this ->responses ) as $ response => $ chunk ) {
130+ try {
131+ if ($ chunk ->isTimeout () && !$ errorOnTimeout ) {
132+ continue ;
133+ }
134+ if (!$ chunk ->isFirst () && !$ chunk ->isLast ()) {
135+ continue ;
136+ }
137+ if ($ chunk ->isLast ()) {
138+ $ this ->responses ->detach ($ response );
139+ }
140+ } catch (ExceptionInterface $ e ) {
121141 $ this ->responses ->detach ($ response );
122142 }
123143 }
144+
145+ if ($ e ) {
146+ error_log (sprintf ("Could not push logs to Elasticsearch: \n%s " , (string ) $ e ));
147+ }
124148 }
125149}
0 commit comments