Best Practices for Observability Deployment of RuoYi Monolithic Applications on Tomcat¶
Based on the RuoYi monolithic application, this document demonstrates best practices for observability in an external Tomcat environment.
Implementation Goals¶
- Collect Metrics information
- Collect Trace information
- Collect LOG information
- Collect RUM information
- SESSION REPLAY This refers to the recording of user sessions when accessing the frontend, including button clicks, interface operations, and dwell times. It helps in understanding customer intent and reproducing operations.
Version Information¶
- Tomcat (9.0.81)
- Springboot (2.6.2)
- JDK (>=8)
- DDTrace (>=1.0)
Special Notes
For Springboot projects, the major version of Tomcat must align with the major version of the embedded Tomcat in Springboot; otherwise, there may be startup exceptions.
RuoYi Monolithic Application¶
- Download Source Code
- Remove Internal Tomcat
Adjust the pom.xml
file in the project root directory:
......
<dependencyManagement>
<dependencies>
<!-- SpringBoot dependency configuration -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.5.15</version>
<type>pom</type>
<scope>import</scope>
<!-- Remove internal tomcat -->
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
......
war
Output
Adjust the pom.xml
file under the ruoyi-admin
module:
- Adjust Logs
Add a new logback-spring.xml
file in ruoyi-admin/src/main/resources
. The original content is as follows:
logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- Log storage path -->
<property name="log.path" value="/home/root/ruoyi/logs" />
<!-- Log output format -->
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - [%method,%line] %X{dd.service} %X{dd.trace_id} %X{dd.span_id} - %msg%n" />
<!-- Console output -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- System log output -->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-info.log</file>
<!-- Rolling policy: create log files based on time -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- Log filename format -->
<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- Maximum history of logs 60 days -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- Filter level -->
<level>INFO</level>
<!-- Action when matched: accept (record) -->
<onMatch>ACCEPT</onMatch>
<!-- Action when unmatched: deny (do not record) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-error.log</file>
<!-- Rolling policy: create log files based on time -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- Log filename format -->
<fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- Maximum history of logs 60 days -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- Filter level -->
<level>ERROR</level>
<!-- Action when matched: accept (record) -->
<onMatch>ACCEPT</onMatch>
<!-- Action when unmatched: deny (do not record) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- User access log output -->
<appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-user.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- Roll daily -->
<fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- Maximum history of logs 60 days -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- System module log level control -->
<logger name="com.ruoyi" level="info" />
<!-- Spring log level control -->
<logger name="org.springframework" level="warn" />
<root level="info">
<appender-ref ref="console" />
</root>
<!-- System operation logs -->
<root level="debug">
<appender-ref ref="file_info" />
<appender-ref ref="file_error" />
</root>
<!-- System user operation logs -->
<logger name="sys-user" level="info">
<appender-ref ref="sys-user"/>
</logger>
</configuration>
- Compile
Run the following command in the project root directory to compile:
If Maven is not installed, you need to install Maven first before compiling.
[INFO] --- spring-boot:2.5.15:repackage (default) @ ruoyi-admin ---
[INFO] Replacing main artifact with repackaged archive
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for ruoyi 4.7.7:
[INFO]
[INFO] ruoyi .............................................. SUCCESS [ 0.179 s]
[INFO] ruoyi-common ....................................... SUCCESS [ 4.622 s]
[INFO] ruoyi-system ....................................... SUCCESS [ 0.770 s]
[INFO] ruoyi-framework .................................... SUCCESS [ 0.950 s]
[INFO] ruoyi-quartz ....................................... SUCCESS [ 0.388 s]
[INFO] ruoyi-generator .................................... SUCCESS [ 0.378 s]
[INFO] ruoyi-admin ........................................ SUCCESS [ 4.554 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 12.287 s
[INFO] Finished at: 2023-10-13T16:30:12+08:00
[INFO] ------------------------------------------------------------------------
DataKit¶
- Install DataKit
- Enable collectors
Install DataKit¶
Refer to the DataKit Installation Documentation
Enable DDTrace Collector in DataKit¶
DDTrace Collector: Used to collect trace information from applications. Refer to the DDTrace Collector integration documentation.
Enable Log Collector in DataKit¶
Log Collector: Used to collect log information. Refer to the Log Collector integration documentation.
The following information needs to be adjusted:
logfiles = [
"/home/liurui/ruoyi/logs/*.log",
]
## Add service tag, if it's empty, use $source.
service = "ruoyi"
## Grok pipeline script name.
pipeline = "ruoyi.p"
- logfiles: Path to the log files that need to be collected
- service: Service name
pipeline
: Log parsing
Pipeline Configuration
Pipeline is used for data governance, primarily extracting log information here to associate with trace information.
Create a ruoyi.p
file in the datakit/pipeline/
directory with the following content:
grok(_, "%{TIMESTAMP_ISO8601:time} %{NOTSPACE:thread_name} %{LOGLEVEL:status}%{SPACE}%{NOTSPACE:class_name} - \\[%{NOTSPACE:method_name},%{NUMBER:line}\\] %{DATA:service_name2} %{DATA:trace_id} %{DATA:span_id} - %{GREEDYDATA:msg}")
default_time(time,"Asia/Shanghai")
Enable StatsD Collector in DataKit¶
StatsD Collector: Used to collect metrics information. Refer to the StatsD Collector integration documentation.
Enable RUM Collector in DataKit¶
RUM Collector: The RUM (Real User Monitor) collector is used to collect user monitoring data reported from web pages or mobile ends. Refer to the RUM Collector integration documentation.
Restart DataKit¶
DDTrace¶
Download dd-trace-java, preferably the latest version.
Create RUM¶
- Login to Guance
- Select
Synthetic Tests
, chooseService List
, and clickCreate
. - Fill in the
Application Name
asruoyi-admin
. TheApplication ID
can be customized or generated randomly by clicking theRandom Generation
button. - Select
web
forApplication Type
. On the right side, there are several types ofSDK Configurations
. Here we selectCDN Synchronous Loading
. Copy the corresponding script content, which will be used later. - Click the
Create
button to complete creation.
Tomcat¶
Download Tomcat¶
Download the corresponding version of Tomcat
Configure DDTrace¶
Add a new script file setenv.sh
in the Tomcat bin
directory:
export CATALINA_OPTS="-javaagent:/home/root/agent/dd-java-agent-1.14.0-guance.jar \
-Ddd.tags=env:test \
-Ddd.jmxfetch.enabled=true \
-Ddd.jmxfetch.statsd.host=localhost \
-Ddd.jmxfetch.statsd.port=8125 \
-Ddd.jmxfetch.tomcat.enabled=true\
-Dlogging.config=classpath:logback-spring.xml"
javaagent
: Specifies theddtace
directoryDlogging.config
: Specifies that the application's logs are output usinglogback
. If the application internally uses log4j, specify the corresponding file.
Deploy Application¶
Copy the packaged application RuoYi/ruoyi-admin/target/ruoyi-admin.war
to the webapps
directory of Tomcat.
Start Tomcat¶
Execute bin/startup.sh
apache-tomcat-9.0.81/bin$ ./startup.sh
Using CATALINA_BASE: /home/root/middleware/apache-tomcat-9.0.81
Using CATALINA_HOME: /home/root/middleware/apache-tomcat-9.0.81
Using CATALINA_TMPDIR: /home/root/middleware/apache-tomcat-9.0.81/temp
Using JRE_HOME: /home/root/middleware/jdk/jdk-11.0.18
Using CLASSPATH: /home/root/middleware/apache-tomcat-9.0.81/bin/bootstrap.jar:/home/root/middleware/apache-tomcat-9.0.81/bin/tomcat-juli.jar
Using CATALINA_OPTS: -javaagent:/home/root/agent/dd-java-agent-1.14.0-guance.jar -Ddd.tags=env:test -Ddd.jmxfetch.enabled=true -Ddd.jmxfetch.statsd.host=localhost -Ddd.jmxfetch.statsd.port=8125 -Ddd.jmxfetch.tomcat.enabled=true -Dlogging.config=classpath:logback-spring.xml
Tomcat started.
Integrate RUM¶
After starting Tomcat, the war
application automatically unzips. Navigate to /webapps/ruoyi-admin/WEB-INF/classes/templates
, adjust include.html
, and paste the script code copied in the previous step into the head
.
<head th:fragment=header(title)>
...
<script src="https://static.guance.com/browser-sdk/v3/dataflux-rum.js" type="text/javascript"></script>
<script>
window.DATAFLUX_RUM &&
window.DATAFLUX_RUM.init({
applicationId: 'APP_ID',
datakitOrigin: 'http://localhost:9529', // Protocol (including ://), domain (or IP address)[and port number]
env: 'production',
version: '1.0.0',
service: 'browser',
sessionSampleRate: 100,
sessionReplaySampleRate: 70,
trackInteractions: true,
traceType: 'ddtrace', // Optional, default is ddtrace. Currently supports ddtrace, zipkin, skywalking_v3, jaeger, zipkin_single_header, w3c_traceparent six types
allowedTracingOrigins: ['http://localhost:8080','http://localhost:8080/ruoyi-admin'], // Optional, allows all requests where headers needed for trace collection can be injected. Can be request origin or regex
});
window.DATAFLUX_RUM && window.DATAFLUX_RUM.startSessionReplayRecording()
</script>
...
</head>
- applicationId: No adjustment needed if copied correctly.
- datakitOrigin: Address of DataKit receiving RUM data
- allowedTracingOrigins: Links front-end APM with API calls initiated by the front end, adding necessary Header information for Trace in the corresponding API interface
Results¶
Access http://localhost:8080/ruoyi-admin
, default username: admin
, password: admin123
.
Logs¶
In the log details, you can view the associated trace information.
Trace Information¶
You can also view log and metric information through traces.