16
16
use Monolog \Handler \AbstractHandler ;
17
17
use Monolog \Logger ;
18
18
use Symfony \Component \HttpClient \HttpClient ;
19
+ use Symfony \Contracts \HttpClient \Exception \ExceptionInterface ;
19
20
use Symfony \Contracts \HttpClient \HttpClientInterface ;
20
21
21
22
/**
22
23
* Push logs directly to Elasticsearch and format them according to Logstash specification.
23
24
*
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
26
27
* answer. Even if all HTTP calls are done asynchronously.
27
28
*
28
29
* 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.
30
31
*
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.
35
37
*
36
38
* @author Grégoire Pineau <[email protected] >
37
39
*/
@@ -45,7 +47,7 @@ class ElasticsearchLogstashHandler extends AbstractHandler
45
47
public function __construct (string $ endpoint = 'http://127.0.0.1:9200 ' , string $ index = 'monolog ' , HttpClientInterface $ client = null , int $ level = Logger::DEBUG , bool $ bubble = true )
46
48
{
47
49
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__ ));
49
51
}
50
52
51
53
parent ::__construct ($ level , $ bubble );
@@ -63,20 +65,16 @@ public function handle(array $record): bool
63
65
64
66
$ this ->sendToElasticsearch ([$ record ]);
65
67
66
- return false === $ this ->bubble ;
68
+ return ! $ this ->bubble ;
67
69
}
68
70
69
71
public function handleBatch (array $ records ): void
70
72
{
71
- $ records = array_filter ($ records , function (array $ record ) {
72
- return $ this ->isHandling ($ record );
73
- });
73
+ $ records = array_filter ($ records , [$ this , 'isHandling ' ]);
74
74
75
- if (! $ records ) {
76
- return ;
75
+ if ($ records ) {
76
+ $ this -> sendToElasticsearch ( $ records ) ;
77
77
}
78
-
79
- $ this ->sendToElasticsearch ($ records );
80
78
}
81
79
82
80
protected function getDefaultFormatter (): FormatterInterface
@@ -116,10 +114,36 @@ private function sendToElasticsearch(array $records)
116
114
117
115
$ this ->responses ->attach ($ response );
118
116
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 ) {
121
141
$ this ->responses ->detach ($ response );
122
142
}
123
143
}
144
+
145
+ if ($ e ) {
146
+ error_log (sprintf ("Could not push logs to Elasticsearch: \n%s " , (string ) $ e ));
147
+ }
124
148
}
125
149
}
0 commit comments