Logging Queries with Spring Data Cassandra – 用Spring Data Cassandra记录查询

最后修改: 2021年 11月 8日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Apache Cassandra is a scalable, distributed NoSQL database. Cassandra streams data between nodes and provides continuous availability with no single point of failure. In fact, Cassandra is able to handle large amounts of data with exceptional performance

Apache Cassandra是一个可扩展、分布式NoSQL数据库。Cassandra在节点之间进行数据流,并提供持续的可用性,没有单点故障。事实上,Cassandra能够以卓越的性能处理大量的数据。

When developing an application that uses a database, it is highly important to be able to log and debug the executed queries. In this tutorial, we’ll be looking at how to log queries and statements when using Apache Cassandra with Spring Boot.

在开发一个使用数据库的应用程序时,能够记录和调试所执行的查询是非常重要的。在本教程中,我们将研究如何在使用Apache Cassandra和Spring Boot时记录查询和语句。

In our examples, we’ll make use of the Spring Data repository abstraction and the Testcontainers library. We’ll see how to configure Cassandra query logging through Spring configuration. In addition, we’ll explore the Datastax Request Logger. We can configure this built-in component for more advanced logging.

在我们的例子中,我们将利用Spring Data仓库抽象和Testcontainers库。我们将看到如何通过Spring配置来配置Cassandra查询日志。此外,我们还将探讨Datastax请求记录器。我们可以配置这个内置组件以实现更高级的日志记录。

2. Setting Up a Test Environment

2.建立一个测试环境

In order to demonstrate query logging, we’ll need to set up a test environment. To begin with, we’ll set up test data using Spring Data for Apache Cassandra. Next, we’ll make use of the Testcontainers library to run a Cassandra database container for integration testing.

为了演示查询日志,我们需要设置一个测试环境。首先,我们将使用Spring Data for Apache Cassandra设置测试数据。接下来,我们将利用Testcontainers库来运行一个Cassandra数据库容器进行集成测试。

2.1. Cassandra Repository

2.1.卡桑德拉资源库

Spring Data enables us to create Cassandra repositories based on common Spring interfaces. First, let’s start by defining a simple DAO class:

Spring Data使我们能够基于常见的Spring接口来创建Cassandra资源库。首先,让我们从定义一个简单的DAO类开始。

@Table
public class Person {

    @PrimaryKey
    private UUID id;
    private String firstName;
    private String lastName;

    public Person(UUID id, String firstName, String lastName) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
    }

    // getters, setters, equals and hash code
}

Then, we’ll define a Spring Data repository for our DAO by extending the CassandraRepository interface:

然后,我们将通过扩展CassandraRepository接口为我们的DAO定义一个Spring Data存储库。

@Repository
public interface PersonRepository extends CassandraRepository<Person, UUID> {}

Finally, we’ll add two properties in our application.properties file:

最后,我们将在我们的application.properties文件中添加两个属性。

spring.data.cassandra.schema-action=create_if_not_exists
spring.data.cassandra.local-datacenter=datacenter1

As a result, Spring Data will automatically create the annotated table for us.

因此,Spring Data将自动为我们创建注释表。

We should note that create_if_not_exists option is not recommended for production systems.

我们应该注意,create_if_not_exists选项不建议用于生产系统。

As an alternative, tables can be created by loading a schema.sql script from the standard root classpath.

作为一种选择,可以通过从标准根classpath加载schema.sql脚本来创建表。

2.2. Cassandra Container

2.2 卡桑德拉容器

As a next step, let’s configure and expose a Cassandra container on a specific port:

作为下一步,让我们在一个特定的端口上配置并公开一个Cassandra容器。

@Container
public static final CassandraContainer cassandra = 
  (CassandraContainer) new CassandraContainer("cassandra:3.11.2").withExposedPorts(9042);

Before using the container for integration testing, we’ll need to override test properties required for Spring Data to establish a connection with it:

在使用容器进行集成测试之前,我们需要覆盖Spring Data所需的测试属性,以便与之建立连接。

TestPropertyValues.of(
  "spring.data.cassandra.keyspace-name=" + KEYSPACE_NAME,
  "spring.data.cassandra.contact-points=" + cassandra.getContainerIpAddress(),
  "spring.data.cassandra.port=" + cassandra.getMappedPort(9042)
).applyTo(configurableApplicationContext.getEnvironment());

createKeyspace(cassandra.getCluster());

Finally, before creating any objects/tables, we’ll need to create a Cassandra keyspace. A keyspace is similar to a database in an RDBMS.

最后,在创建任何对象/表之前,我们需要创建一个Cassandra密钥空间。密钥空间类似于RDBMS中的数据库。

2.3. Integration Tests

2.3.集成测试

Now, we have everything in place to start writing our integration tests.

现在,我们已经准备好了一切,可以开始编写我们的集成测试。

We are interested in logging select, insert, and delete queries. Thus, we will write a couple of tests that will trigger those different types of queries.

我们对记录选择、插入和删除查询感兴趣。因此,我们将编写几个测试,以触发这些不同类型的查询。

First, we’ll write a test for saving and updating a person’s record. We expect this test to execute two inserts and one select database query:

首先,我们要写一个保存和更新一个人的记录的测试。我们希望这个测试能执行两次插入和一次选择数据库查询。

@Test
void givenExistingPersonRecord_whenUpdatingIt_thenRecordIsUpdated() {
    UUID personId = UUIDs.timeBased();
    Person existingPerson = new Person(personId, "Luka", "Modric");
    personRepository.save(existingPerson);
    existingPerson.setFirstName("Marko");
    personRepository.save(existingPerson);

    List<Person> savedPersons = personRepository.findAllById(List.of(personId));
    assertThat(savedPersons.get(0).getFirstName()).isEqualTo("Marko");
}

Then, we’ll write a test for saving and deleting an existing person record. We expect this test to execute one insert, delete and select database queries:

然后,我们将写一个保存和删除现有人员记录的测试。我们希望这个测试能执行一个插入、删除和选择的数据库查询。

@Test
void givenExistingPersonRecord_whenDeletingIt_thenRecordIsDeleted() {
    UUID personId = UUIDs.timeBased();
    Person existingPerson = new Person(personId, "Luka", "Modric");

    personRepository.delete(existingPerson);

    List<Person> savedPersons = personRepository.findAllById(List.of(personId));
    assertThat(savedPersons.isEmpty()).isTrue();
}

By default, we won’t observe any database queries logged in the console.

默认情况下,我们不会观察到控制台中记录的任何数据库查询。

3. Spring Data CQL Logging

3.Spring Data CQL日志

With Spring Data for Apache Cassandra version 2.0 or higher, is possible to set the log level for the CqlTemplate class in the application.properties:

在Spring Data for Apache Cassandra 2.0或更高版本中,可以在 application.properties中为CqlTemplate类设置日志级别。

logging.level.org.springframework.data.cassandra.core.cql.CqlTemplate=DEBUG

Thus, by setting the log level to DEBUG, we enable logging of all executed queries and prepared statements:

因此,通过设置日志级别为DEBUG,我们可以对所有执行的查询和准备好的语句进行记录。

2021-09-25 12:41:58.679 DEBUG 17856 --- [           main] o.s.data.cassandra.core.cql.CqlTemplate:
  Executing CQL statement [CREATE TABLE IF NOT EXISTS person
  (birthdate date, firstname text, id uuid, lastname text, lastpurchaseddate timestamp, lastvisiteddate timestamp, PRIMARY KEY (id));]
2021-09-25 12:42:01.204 DEBUG 17856 --- [           main] o.s.data.cassandra.core.cql.CqlTemplate:
  Preparing statement [INSERT INTO person (birthdate,firstname,id,lastname,lastpurchaseddate,lastvisiteddate)
  VALUES (?,?,?,?,?,?)] using org.springframework.data.cassandra.core.CassandraTemplate$PreparedStatementHandler@4d16975b
2021-09-25 12:42:01.253 DEBUG 17856 --- [           main] o.s.data.cassandra.core.cql.CqlTemplate:
  Executing prepared statement [INSERT INTO person (birthdate,firstname,id,lastname,lastpurchaseddate,lastvisiteddate) VALUES (?,?,?,?,?,?)]
2021-09-25 12:42:01.279 DEBUG 17856 --- [           main] o.s.data.cassandra.core.cql.CqlTemplate:
  Preparing statement [INSERT INTO person (birthdate,firstname,id,lastname,lastpurchaseddate,lastvisiteddate)
  VALUES (?,?,?,?,?,?)] using org.springframework.data.cassandra.core.CassandraTemplate$PreparedStatementHandler@539dd2d0
2021-09-25 12:42:01.290 DEBUG 17856 --- [           main] o.s.data.cassandra.core.cql.CqlTemplate:
  Executing prepared statement [INSERT INTO person (birthdate,firstname,id,lastname,lastpurchaseddate,lastvisiteddate) VALUES (?,?,?,?,?,?)]
2021-09-25 12:42:01.351 DEBUG 17856 --- [           main] o.s.data.cassandra.core.cql.CqlTemplate:
  Preparing statement [SELECT * FROM person WHERE id IN (371bb4a0-1ded-11ec-8cad-934f1aec79e6)]
  using org.springframework.data.cassandra.core.CassandraTemplate$PreparedStatementHandler@3e61cffd
2021-09-25 12:42:01.370 DEBUG 17856 --- [           main] o.s.data.cassandra.core.cql.CqlTemplate:
  Executing prepared statement [SELECT * FROM person WHERE id IN (371bb4a0-1ded-11ec-8cad-934f1aec79e6)]

Unfortunately, using this solution, we won’t see the output of the bound values used in the statements.

不幸的是,使用这种解决方案,我们不会看到语句中使用的绑定值的输出。

4. Datastax Request Tracker

4.Datastax请求跟踪器

The DataStax request tracker is a session-wide component that gets notified about the outcome of every Cassandra request.

DataStax请求跟踪器是一个会话范围内的组件,可获得有关每个Cassandra请求结果的通知

The DataStax Java driver for Apache Cassandra comes with an optional request tracker implementation that logs all requests.

用于Apache Cassandra的DataStax Java驱动程序带有一个可选的请求跟踪器实现,可以记录所有请求。

4.1. Noop Request Tracker

4.1.Noop请求跟踪器

The default request tracker implementation is called NoopRequestTracker. Consequently, it doesn’t do anything:

默认的请求跟踪器实现被称为NoopRequestTracker。因此,它不做任何事情。

System.setProperty("datastax-java-driver.advanced.request-tracker.class", "NoopRequestTracker");

To set up a different tracker, we should specify a class that implements RequestTracker in the Cassandra configuration or via system properties.

要设置不同的跟踪器,我们应该在 Cassandra 配置中或通过系统属性指定一个实现RequestTracker的类。

4.2. Request Logger

4.2 请求记录器

RequestLogger is a built-in implementation of the RequestTracker that logs every request.

RequestLoggerRequestTracker的一个内置实现,记录每个请求

We can enable it by setting specific DataStax Java driver system properties:

我们可以通过设置特定的DataStax Java驱动系统属性来启用它。

System.setProperty("datastax-java-driver.advanced.request-tracker.class", "RequestLogger");
System.setProperty("datastax-java-driver.advanced.request-tracker.logs.success.enabled", "true");
System.setProperty("datastax-java-driver.advanced.request-tracker.logs.slow.enabled", "true");
System.setProperty("datastax-java-driver.advanced.request-tracker.logs.error.enabled", "true");

In this example, we enabled logging of all successful, slow, and failed requests.

在这个例子中,我们启用了对所有成功、缓慢和失败的请求的记录。

Now, when we run our tests, we’ll observe all executed database queries in the log:

现在,当我们运行我们的测试时,我们将在日志中观察到所有执行的数据库查询。

2021-09-25 13:06:31.799  INFO 11172 --- [        s0-io-4] c.d.o.d.i.core.tracker.RequestLogger:
  [s0|90232530][Node(endPoint=localhost/[0:0:0:0:0:0:0:1]:49281, hostId=c50413d5-03b6-4037-9c46-29f0c0da595a, hashCode=68c305fe)]
  Success (6 ms) [6 values] INSERT INTO person (birthdate,firstname,id,lastname,lastpurchaseddate,lastvisiteddate)
  VALUES (?,?,?,?,?,?) [birthdate=NULL, firstname='Luka', id=a3ad6890-1df0-11ec-a295-7d319da1858a, lastname='Modric', lastpurchaseddate=NULL, lastvisiteddate=NULL]
2021-09-25 13:06:31.811  INFO 11172 --- [        s0-io-4] c.d.o.d.i.core.tracker.RequestLogger:
  [s0|778232359][Node(endPoint=localhost/[0:0:0:0:0:0:0:1]:49281, hostId=c50413d5-03b6-4037-9c46-29f0c0da595a, hashCode=68c305fe)]
  Success (4 ms) [6 values] INSERT INTO person (birthdate,firstname,id,lastname,lastpurchaseddate,lastvisiteddate)
  VALUES (?,?,?,?,?,?) [birthdate=NULL, firstname='Marko', id=a3ad6890-1df0-11ec-a295-7d319da1858a, lastname='Modric', lastpurchaseddate=NULL, lastvisiteddate=NULL]
2021-09-25 13:06:31.847  INFO 11172 --- [        s0-io-4] c.d.o.d.i.core.tracker.RequestLogger:
  [s0|1947131919][Node(endPoint=localhost/[0:0:0:0:0:0:0:1]:49281, hostId=c50413d5-03b6-4037-9c46-29f0c0da595a, hashCode=68c305fe)]
  Success (5 ms) [0 values] SELECT * FROM person WHERE id IN (a3ad6890-1df0-11ec-a295-7d319da1858a)

We’ll see all the requests are logged under the category com.datastax.oss.driver.internal.core.tracker.RequestLogger.

我们会看到所有的请求都被记录在com.datastax.oss.driver.internal.core.tracker.RequestLogger类别下。

In addition, all bound values used in the statements also get logged per default.

此外,语句中使用的所有绑定值也会按默认情况被记录下来

4.3. Bound Values

4.3.边界值

The built-it RequestLogger is a highly customizable component. We can configure the output of the bound values using the following system properties:

内置的RequestLogger是一个高度可定制的组件。我们可以通过以下系统属性来配置绑定值的输出。

System.setProperty("datastax-java-driver.advanced.request-tracker.logs.show-values", "true");
System.setProperty("datastax-java-driver.advanced.request-tracker.logs.max-value-length", "100");
System.setProperty("datastax-java-driver.advanced.request-tracker.logs.max-values", "100");

A formatted representation of a value will be truncated in case it is longer than the value defined by the max-value-length property.

如果一个值的格式化表示长于max-value-length属性定义的值,它将被截断。

Using the max-values property, we can define the maximum number of bound values to log.

使用max-values 属性,我们可以定义要记录的最大数量的绑定值。

4.4. Additional Options

4.4.附加选项

In our first example, we enabled the logging of slow requests. We can use the threshold property to classify a successful request as slow:

在我们的第一个例子中,我们启用了慢速请求的日志记录。我们可以使用threshold属性将一个成功的请求归为慢速

System.setProperty("datastax-java-driver.advanced.request-tracker.logs.slow.threshold ", "1 second");

By default, stack traces are logged for all failed requests. In case we disable them, we’ll only see the exception’s string representation in the log:

默认情况下,所有失败的请求都有堆栈跟踪记录。如果我们禁用它们,我们将在日志中只看到异常的字符串表示。

System.setProperty("datastax-java-driver.advanced.request-tracker.logs.show-stack-trace", "true");

Successful and slow requests use the INFO log level. On the other hand, failed requests use the ERROR level.

成功和缓慢的请求使用INFO日志级别。另一方面,失败的请求则使用ERROR级别。

5. Conclusion

5.总结

In this article, we explored the logging of queries and statements when using Apache Cassandra with Spring Boot.

在这篇文章中,我们探讨了在使用Apache Cassandra与Spring Boot时对查询和语句进行记录的问题

In the examples, we covered configuring the log level in Spring Data for Apache Cassandra. We saw that Spring Data will log queries, but not the bound values. Finally, we explored the Datastax Request Tracker. It is a highly customizable component we can use to log Cassandra queries together with their bound values.

在例子中,我们介绍了配置Apache Cassandra的Spring Data中的日志级别。我们看到,Spring Data会记录查询,但不会记录绑定值。最后,我们探讨了Datastax Request Tracker。它是一个高度可定制的组件,我们可以用它来记录Cassandra的查询和它们的绑定值。

As always, the source code is available over on GitHub

一如既往,源代码可在GitHub上获得