Skip to content

Commit 2d585b6

Browse files
authored
Support error log handler for Http server (#3700)
Fixes #3257 Signed-off-by: raccoonback <[email protected]>
1 parent 7028297 commit 2d585b6

File tree

23 files changed

+1326
-31
lines changed

23 files changed

+1326
-31
lines changed

docs/modules/ROOT/pages/http-server.adoc

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -412,10 +412,13 @@ The following example uses a domain name containing a wildcard:
412412
include::{examples-dir}/sni/Application.java[lines=18..47]
413413
----
414414

415-
[[http-access-log]]
416-
== HTTP Access Log
415+
[[http-log]]
416+
== HTTP Log
417417

418-
You can enable the `HTTP` access log either programmatically or by configuration. By default, it is disabled.
418+
You can enable the `HTTP` access or error log either programmatically or by configuration. By default, it is disabled.
419+
420+
[[access-log]]
421+
=== Access Log
419422

420423
You can use `-Dreactor.netty.http.server.accessLogEnabled=true` to enable the `HTTP` access log by configuration.
421424

@@ -472,6 +475,63 @@ include::{examples-dir}/accessLog/CustomFormatAndFilterAccessLogApplication.java
472475
<1> Specifies the filter predicate to use
473476
<2> Specifies the custom format to apply
474477

478+
[[error-log]]
479+
=== Error Log
480+
481+
You can use `-Dreactor.netty.http.server.errorLogEnabled=true` to enable the `HTTP` error log by configuration.
482+
483+
You can use the following configuration (for Logback or similar logging frameworks) to have a separate
484+
`HTTP` error log file:
485+
486+
[source,xml]
487+
----
488+
<appender name="errorLog" class="ch.qos.logback.core.FileAppender">
489+
<file>error_log.log</file>
490+
<encoder>
491+
<pattern>%msg%n</pattern>
492+
</encoder>
493+
</appender>
494+
<appender name="async" class="ch.qos.logback.classic.AsyncAppender">
495+
<appender-ref ref="errorLog" />
496+
</appender>
497+
498+
<logger name="reactor.netty.http.server.ErrorLog" level="ERROR" additivity="false">
499+
<appender-ref ref="async"/>
500+
</logger>
501+
----
502+
503+
The following example enables it programmatically:
504+
505+
{examples-link}/errorLog/Application.java
506+
----
507+
include::{examples-dir}/errorLog/Application.java[lines=21..32]
508+
----
509+
510+
Calling this method takes precedence over the system property configuration.
511+
512+
By default, the logging format is `[{datetime}] [error] [client {remote address}] {exception message}`, but you can
513+
specify a custom one as a parameter, as in the following example:
514+
515+
{examples-link}/errorLog/CustomLogErrorFormatApplication.java
516+
----
517+
include::{examples-dir}/errorLog/CustomLogErrorFormatApplication.java[lines=22..36]
518+
----
519+
520+
You can also filter `HTTP` error logs by using the `ErrorLogFactory#createFilter` method, as in the following example:
521+
522+
{examples-link}/errorLog/FilterLogErrorApplication.java
523+
----
524+
include::{examples-dir}/errorLog/FilterLogErrorApplication.java[lines=22..36]
525+
----
526+
527+
Note that this method can take a custom format parameter too, as in this example:
528+
529+
{examples-link}/errorLog/CustomFormatAndFilterErrorLogApplication.java.java
530+
----
531+
include::{examples-dir}/errorLog/CustomFormatAndFilterErrorLogApplication.java[lines=23..40]
532+
----
533+
<1> Specifies the filter predicate to use
534+
<2> Specifies the custom format to apply
475535

476536
[[HTTP2]]
477537
== HTTP/2

reactor-netty-core/src/main/java/reactor/netty/NettyPipeline.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2011-2024 VMware, Inc. or its affiliates, All Rights Reserved.
2+
* Copyright (c) 2011-2025 VMware, Inc. or its affiliates, All Rights Reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -89,6 +89,7 @@ public interface NettyPipeline {
8989
String RIGHT = "reactor.right.";
9090

9191
String AccessLogHandler = LEFT + "accessLogHandler";
92+
String ErrorLogHandler = LEFT + "errorLogHandler";
9293
String ChannelMetricsHandler = LEFT + "channelMetricsHandler";
9394
String ChunkedWriter = LEFT + "chunkedWriter";
9495
String CompressionHandler = LEFT + "compressionHandler";

reactor-netty-core/src/main/java/reactor/netty/ReactorNetty.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2011-2024 VMware, Inc. or its affiliates, All Rights Reserved.
2+
* Copyright (c) 2011-2025 VMware, Inc. or its affiliates, All Rights Reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -194,6 +194,12 @@ public final class ReactorNetty {
194194
*/
195195
public static final String ACCESS_LOG_ENABLED = "reactor.netty.http.server.accessLogEnabled";
196196

197+
/**
198+
* Specifies whether the Http Server error log will be enabled.
199+
* By default, it is disabled.
200+
*/
201+
public static final String ERROR_LOG_ENABLED = "reactor.netty.http.server.errorLogEnabled";
202+
197203
/**
198204
* Specifies the zone id used by the access log.
199205
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright (c) 2025 VMware, Inc. or its affiliates, All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package reactor.netty.examples.documentation.http.server.errorLog;
17+
18+
import reactor.netty.DisposableServer;
19+
import reactor.netty.http.server.HttpServer;
20+
21+
public class Application {
22+
23+
public static void main(String[] args) {
24+
DisposableServer server =
25+
HttpServer.create()
26+
.errorLog(true)
27+
.bindNow();
28+
29+
server.onDispose()
30+
.block();
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright (c) 2025 VMware, Inc. or its affiliates, All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package reactor.netty.examples.documentation.http.server.errorLog;
17+
18+
import reactor.netty.DisposableServer;
19+
import reactor.netty.http.server.HttpServer;
20+
import reactor.netty.http.server.logging.error.DefaultErrorLog;
21+
import reactor.netty.http.server.logging.error.ErrorLogFactory;
22+
23+
public class CustomFormatAndFilterErrorLogApplication {
24+
25+
public static void main(String[] args) {
26+
DisposableServer server =
27+
HttpServer.create()
28+
.errorLog(
29+
true,
30+
ErrorLogFactory.createFilter(
31+
p -> p.cause() instanceof RuntimeException, //<1>
32+
x -> DefaultErrorLog.create("method={}, uri={}", x.httpServerInfos().method(), x.httpServerInfos().uri()) //<2>
33+
)
34+
)
35+
.bindNow();
36+
37+
server.onDispose()
38+
.block();
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright (c) 2025 VMware, Inc. or its affiliates, All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package reactor.netty.examples.documentation.http.server.errorLog;
17+
18+
import reactor.netty.DisposableServer;
19+
import reactor.netty.http.server.HttpServer;
20+
import reactor.netty.http.server.logging.error.DefaultErrorLog;
21+
22+
public class CustomLogErrorFormatApplication {
23+
24+
public static void main(String[] args) {
25+
DisposableServer server =
26+
HttpServer.create()
27+
.errorLog(
28+
true,
29+
x -> DefaultErrorLog.create("method={}, uri={}", x.httpServerInfos().method(), x.httpServerInfos().uri())
30+
)
31+
.bindNow();
32+
33+
server.onDispose()
34+
.block();
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright (c) 2021-2025 VMware, Inc. or its affiliates, All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package reactor.netty.examples.documentation.http.server.errorLog;
17+
18+
import reactor.netty.DisposableServer;
19+
import reactor.netty.http.server.HttpServer;
20+
import reactor.netty.http.server.logging.error.ErrorLogFactory;
21+
22+
public class FilterLogErrorApplication {
23+
24+
public static void main(String[] args) {
25+
DisposableServer server =
26+
HttpServer.create()
27+
.errorLog(
28+
true,
29+
ErrorLogFactory.createFilter(p -> p.cause() instanceof RuntimeException)
30+
)
31+
.bindNow();
32+
33+
server.onDispose()
34+
.block();
35+
}
36+
}

reactor-netty-http/src/main/java/reactor/netty/http/server/Http3Codec.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
import reactor.netty.http.server.logging.AccessLog;
3636
import reactor.netty.http.server.logging.AccessLogArgProvider;
3737
import reactor.netty.http.server.logging.AccessLogHandlerFactory;
38+
import reactor.netty.http.server.logging.error.DefaultErrorLogHandler;
39+
import reactor.netty.http.server.logging.error.ErrorLog;
40+
import reactor.netty.http.server.logging.error.ErrorLogArgProvider;
3841
import reactor.util.Logger;
3942
import reactor.util.Loggers;
4043
import reactor.util.annotation.Nullable;
@@ -52,6 +55,8 @@ final class Http3Codec extends ChannelInitializer<QuicStreamChannel> {
5255

5356
final boolean accessLogEnabled;
5457
final Function<AccessLogArgProvider, AccessLog> accessLog;
58+
final boolean errorLogEnabled;
59+
final Function<ErrorLogArgProvider, ErrorLog> errorLog;
5560
final HttpCompressionOptionsSpec compressionOptions;
5661
final BiPredicate<HttpServerRequest, HttpServerResponse> compressPredicate;
5762
final ServerCookieDecoder cookieDecoder;
@@ -74,6 +79,8 @@ final class Http3Codec extends ChannelInitializer<QuicStreamChannel> {
7479
Http3Codec(
7580
boolean accessLogEnabled,
7681
@Nullable Function<AccessLogArgProvider, AccessLog> accessLog,
82+
boolean errorLogEnabled,
83+
@Nullable Function<ErrorLogArgProvider, ErrorLog> errorLog,
7784
@Nullable HttpCompressionOptionsSpec compressionOptions,
7885
@Nullable BiPredicate<HttpServerRequest, HttpServerResponse> compressPredicate,
7986
ServerCookieDecoder decoder,
@@ -93,6 +100,8 @@ final class Http3Codec extends ChannelInitializer<QuicStreamChannel> {
93100
boolean validate) {
94101
this.accessLogEnabled = accessLogEnabled;
95102
this.accessLog = accessLog;
103+
this.errorLogEnabled = errorLogEnabled;
104+
this.errorLog = errorLog;
96105
this.compressionOptions = compressionOptions;
97106
this.compressPredicate = compressPredicate;
98107
this.cookieDecoder = decoder;
@@ -149,6 +158,10 @@ else if (metricsRecorder instanceof ContextAwareHttpServerMetricsRecorder) {
149158
}
150159
}
151160

161+
if (errorLogEnabled) {
162+
p.addBefore(NettyPipeline.ReactiveBridge, NettyPipeline.ErrorLogHandler, new DefaultErrorLogHandler(errorLog));
163+
}
164+
152165
channel.pipeline().remove(this);
153166

154167
if (log.isDebugEnabled()) {
@@ -159,6 +172,8 @@ else if (metricsRecorder instanceof ContextAwareHttpServerMetricsRecorder) {
159172
static ChannelHandler newHttp3ServerConnectionHandler(
160173
boolean accessLogEnabled,
161174
@Nullable Function<AccessLogArgProvider, AccessLog> accessLog,
175+
boolean errorLogEnabled,
176+
@Nullable Function<ErrorLogArgProvider, ErrorLog> errorLog,
162177
@Nullable HttpCompressionOptionsSpec compressionOptions,
163178
@Nullable BiPredicate<HttpServerRequest, HttpServerResponse> compressPredicate,
164179
ServerCookieDecoder decoder,
@@ -177,8 +192,8 @@ static ChannelHandler newHttp3ServerConnectionHandler(
177192
@Nullable Function<String, String> uriTagValue,
178193
boolean validate) {
179194
return new Http3ServerConnectionHandler(
180-
new Http3Codec(accessLogEnabled, accessLog, compressionOptions, compressPredicate, decoder, encoder, formDecoderProvider, forwardedHeaderHandler,
181-
httpMessageLogFactory, listener, mapHandle, methodTagValue, metricsRecorder, minCompressionSize,
182-
opsFactory, readTimeout, requestTimeout, uriTagValue, validate));
195+
new Http3Codec(accessLogEnabled, accessLog, errorLogEnabled, errorLog, compressionOptions, compressPredicate, decoder, encoder,
196+
formDecoderProvider, forwardedHeaderHandler, httpMessageLogFactory, listener, mapHandle, methodTagValue, metricsRecorder,
197+
minCompressionSize, opsFactory, readTimeout, requestTimeout, uriTagValue, validate));
183198
}
184199
}

0 commit comments

Comments
 (0)