Skip to main content

Logging

While optional, we do recommend installing and configuring a logging implementation to aid in debugging and provide useful information for day-to-day operations; plus, it's good practice. Discord4J uses Reactor's logging implementation, which is compatible with any SLF4J implementation. We recommend using Logback for maximum flexibility and customization.

dependencies {
implementation 'ch.qos.logback:logback-classic:$logback_version'
}

Implementation

Discord4J utilizes logging throughout their modules that will be forwarded to your implementation of choice, according to Reactor Loggers class which picks up common logging frameworks on the startup and configures logging appropriately.

  • If you have any SLF4J implementation available, it will be picked up first.
  • As a fallback, it will log to the console, using System.err for the WARN and ERROR log levels and System.out for the rest.
  • If you prefer to log to JDK java.util.logging you must set the reactor.logging.fallback system property to JDK, For example, if running from the command line:
-Dreactor.logging.fallback=JDK

Logging a Stream

You have the ability to log events in a reactive sequence, like those coming from Discord4J. The log() operator is able to do that, peeking at every signal going through a sequence. You can learn more about this operator on the Reactor reference guide here.

Configuration

SLF4J Simple

SLF4J Simple is a basic implementation that outputs INFO and higher logging directly to System.err. It's easy to use and requires no additional files. Check it out here.

Logback

Logback is an SLF4J implementation you can use with Discord4J to further configure logging. The following is an example to use it. First add this dependency to your project. Then create a file under src/main/resources named logback.xml with the following content:

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true">
<!-- You can configure per-logger level at this point -->
<!-- This set of preconfigured loggers is good if you want to have a DEBUG level as baseline -->
<logger name="io.netty" level="INFO"/>
<logger name="reactor" level="INFO"/>

<!-- Display the logs in your console with the following format -->
<!-- You can learn more about this here: https://logback.qos.ch/manual/layouts.html#ClassicPatternLayout -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

<!-- Log to a file as well, including size and time based rolling -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/d4j.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>90</maxHistory>
</rollingPolicy>
<encoder>
<charset>UTF-8</charset>
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %-40.40logger{39} : %msg%n</Pattern>
</encoder>
<prudent>true</prudent>
</appender>

<!-- Avoid blocking while logging to file by wrapping our file appender with async capabilities -->
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>512</queueSize>
<appender-ref ref="FILE"/>
</appender>

<!-- Here you can set the base logger level. If DEBUG is too chatty for you, you can use INFO -->
<!-- Possible options are: ALL, TRACE, DEBUG, INFO, WARN, ERROR, OFF -->
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ASYNC"/>
</root>
</configuration>

Log4J2

Log4J2 can also work with Discord4J using an SLF4J adapter. To begin please add log4j-slf4j-impl as dependency.

Available loggers

Discord4J has a logger structure that differs from v2, where you can tweak at the logger level the verbosity you prefer.

If you want to reduce the logging produced by websocket data you can use the following in your logback.xml file:

<logger name="discord4j.gateway" level="INFO"/>

The following table shows the levels you can set each logger to obtain your preferred details. They work on a hierarchy basis therefore setting a level to discord4j.rest affects every logger under it like discord4j.rest.traces.

LoggerLevelDescription
io.nettyDEBUGLow level details of underlying Netty implementation
reactorDEBUGLow level details for Reactor operations
reactor.nettyDEBUGDetails about Reactor Netty network operations
discord4j.coreINFOVersion information about Discord4J
discord4j.core.eventsDEBUGEvent dispatcher subscription information
discord4j.core.eventsTRACEEvent dispatcher event instances being published
discord4j.core.events.dispatchDEBUGRequests made by Discord4J while converting inbound payloads into events
discord4j.core.events.dispatchTRACEDetails about caching while converting inbound payloads into events
discord4j.core.stateDEBUGDetails about entity cache configuration
discord4j.core.shardDEBUGDetails about shard group bootstrapping
discord4j.rest.requestDEBUGHTTP requests made by Discord4J, for example GET /gateway/bot
discord4j.rest.requestTRACEExtra details about the lifecycle of an HTTP request
discord4j.limiterTRACELifecycle of the default rate limiter implementation
discord4j.rest.http.JacksonWriterStrategyTRACEHTTP request JSON body contents
discord4j.rest.http.JacksonReaderStrategyTRACEHTTP response JSON body contents
discord4j.rest.http.client.DiscordWebClientTRACELifecycle of the REST API client
discord4j.gatewayINFOMain events of Discord Gateway connections (connected, reconnects, disconnects)
discord4j.gatewayDEBUGDetails of Discord Gateway connections (heartbeats, reconnect reasons)
discord4j.gateway.protocol.senderTRACEJSON payload sent to Discord Gateway
discord4j.gateway.protocol.receiverTRACEJSON payload received from Discord Gateway
discord4j.voiceINFOMain events of Discord Voice Gateway connections (connecting, connected, reconnects, disconnects)
discord4j.voiceDEBUGDetails of Discord Gateway connections (heartbeats, reconnect reasons)
reactor.netty.udpDEBUGDetails about Reactor Netty UDP connections (used by voice)
discord4j.voice.protocol.senderTRACEJSON payload sent to Discord Voice Gateway
discord4j.voice.protocol.receiverTRACEJSON payload received from Discord Voice Gateway
discord4j.voice.protocol.udp.senderTRACEJSON payload sent to a Discord Voice Server
discord4j.voice.protocol.udp.receiverTRACEJSON payload received from a Discord Voice Server

Advanced filtering using Logback

If you're looking to filter out certain gateway event types, you could copy this custom TurboFilter to your project or as a start point for your own.

Afterwards, apply it to your logback.xml, for example the following would only show created message events:

<configuration>
<logger name="discord4j.gateway.protocol.receiver" level="TRACE"/>
<turboFilter class="discord4j.core.logback.GatewayEventFilter">
<Logger>discord4j.gateway.protocol.receiver</Logger>
<Include>MESSAGE_CREATE</Include>
</turboFilter>
...

While this one would show all events except presence updates:

<configuration>
<logger name="discord4j.gateway.protocol.receiver" level="TRACE"/>
<turboFilter class="discord4j.core.logback.GatewayEventFilter">
<Logger>discord4j.gateway.protocol.receiver</Logger>
<Exclude>PRESENCE_UPDATE</Exclude>
</turboFilter>
...

The value like PRESENCE_UPDATE must match from the ones in this file.

For complete control over what you want to filter, use JaninoEvaluator. First add janino as a dependency and then apply the following to your logback.xml:

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator>
<expression>return formattedMessage.contains("PresenceUpdate");</expression>
</evaluator>
<OnMismatch>NEUTRAL</OnMismatch>
<OnMatch>DENY</OnMatch>
</filter>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

For more details of what you can put under the <expression> attribute, see this documentation page.