You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
|`com.sap.cds.odata.v2`| Logs OData V2 request handling in the adapter|
152
+
|`com.sap.cds.odata.v4`| Logs OData V4 request handling in the adapter|
153
+
|`com.sap.cds.handlers`| Logs sequence of executed handlers as well as the lifecycle of RequestContexts and ChangeSetContexts|
154
+
|`com.sap.cds.persistence.sql`| Logs executed queries such as CQN and SQL statements (w/o parameters)|
155
+
|`com.sap.cds.persistence.sql-tx`| Logs transactions, ChangeSetContexts, and connection pool|
156
+
|`com.sap.cds.multitenancy`| Logs tenant-related events and sidecar communication|
157
+
|`com.sap.cds.messaging`| Logs messaging configuration and messaging events|
158
+
|`com.sap.cds.remote.odata`| Logs request handling for remote OData calls|
159
+
|`com.sap.cds.remote.wire`| Logs communication of remote OData calls|
160
+
|`com.sap.cds.auditlog`| Logs audit log events|
161
161
162
162
Most of the loggers are used on DEBUG level by default as they produce quite some log output. It's convenient to control loggers on package level, for example, `com.sap.cds.security` covers all loggers that belong to this package (namely `com.sap.cds.security.authentication` and `com.sap.cds.security.authorization`).
163
163
@@ -289,7 +289,7 @@ Step-by-step description on how to access a bash session in the application's co
289
289
1. Locate java executable and JDBC driver:
290
290
291
291
By default `JAVA_HOME` isn't set in the buildpack and contains minimal tooling, as it tries to minimize the container size. However, the default location of the `java` executable is `/layers/paketo-buildpacks_sap-machine/jre/bin`.
292
-
292
+
293
293
For convenience, store the path into a variable, for example, `JAVA_HOME`:
@@ -357,6 +357,10 @@ In addition, it's possible to add manual instrumentations using the [Open Teleme
357
357
The configuration steps below assume that your application uses the [SAP Java Buildpack](https://help.sap.com/docs/btp/sap-business-technology-platform/sap-jakarta-buildpack).
358
358
:::
359
359
360
+
:::tip
361
+
You can conveniently enhance your application with Open Telemetry observability by running the command `cds add cloud-logging --with-telemetry` in your project directory.
362
+
:::
363
+
360
364
Configure your application to enable the Open Telemetry Java Agent by adding or adapting the `JBP_CONFIG_JAVA_OPTS` parameter in your deployment descriptor:
361
365
362
366
::: code-group
@@ -461,7 +465,7 @@ The following steps describe the required configuration:
461
465
By default, instrumentation for CAP-specific components is disabled, so that no traces and spans are created even if the Open Telemetry Java Agent has been configured. It's possible to selectively activate specific spans by changing the log level for a component.
| `metrics` | Thread pools, connection pools, CPU, and memory usage of JVM and HTTP web server |
612
+
| `beans` | Information about Spring beans created in the application |
613
+
| `env` | Exposes the full Spring environment including application configuration |
614
+
| `loggers` | List and modify application loggers |
611
615
612
616
By default, nearly all actuators are active. You can switch off actuators individually in the configuration. The following configuration turns off `flyway` actuator:
@@ -34,7 +34,7 @@ Once the transaction succeeds, the messages are read from the database table and
34
34
35
35
To enable the persistence for the outbox, you need to add the service `outbox` of kind `persistent-outbox` to the `cds.requires` section in the _package.json_ or _cdsrc.json_, which will automatically enhance your CDS model in order to support the persistent outbox.
36
36
37
-
```jsonc
37
+
```jsonc
38
38
{
39
39
// ...
40
40
"cds": {
@@ -124,12 +124,12 @@ public class MySpringComponent {
124
124
... it must be ensured that there are no unprocessed entries left.
125
125
126
126
Removing a custom outbox from the `cds.outbox.services` section doesn't remove the
127
-
entries from the `cds.outbox.Messages` table. The entries remain in the `cds.outbox.Messages` table and isn't
127
+
entries from the `cds.outbox.Messages` table. The entries remain in the `cds.outbox.Messages` table and aren't
128
128
processed anymore.
129
129
130
130
:::
131
131
132
-
### Outbox Event Versions
132
+
### Outbox Event Versions
133
133
134
134
In scenarios with multiple deployment versions (blue/green), situations may arise in which the outbox collectors of the older deployment cannot process the events generated by a newer deployment. In this case, the event can get stuck in the outbox, with all the resulting problems.
If a method on the outboxed service has a return value, it will always return `null` since it is executed asynchronously. A common example for this are the `CqnService.run(...)` methods.
185
+
If a method on the outboxed service has a return value, it will always return `null` since it's executed asynchronously. A common example for this are the `CqnService.run(...)` methods.
186
186
To improve this the API `OutboxService.outboxed(Service, Class)` can be used, which wraps a service with an asynchronous suited API while outboxing it.
187
187
This can be used together with the interface `AsyncCqnService` to outbox remote OData services:
188
188
@@ -212,7 +212,7 @@ A service wrapped by an outbox can be unboxed by calling the API `OutboxService.
212
212
service are executed synchronously without storing the event in an outbox.
213
213
214
214
::: warning Java Proxy
215
-
A service wrapped by an outbox is a [Java Proxy](https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/proxy.html). Such a proxy only implements the _interfaces_ of the object it is wrapping. This means an outboxed service proxy can't be casted to the class implementing the underlying service object.
215
+
A service wrapped by an outbox is a [Java Proxy](https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/proxy.html). Such a proxy only implements the _interfaces_ of the object that it's wrapping. This means an outboxed service proxy can't be casted to the class implementing the underlying service object.
216
216
:::
217
217
218
218
::: tip Custom outbox for scaling
@@ -262,7 +262,7 @@ The outbox by default retries publishing a message, if an error occurs during pr
262
262
This behavior makes applications resilient against unavailability of external systems, which is a typical use case for outbox message processing.
263
263
264
264
However, there might also be situations in which it is not reasonable to retry publishing a message.
265
-
For example, when the processed message causes a semantic error - typically due to a 400 Bad request - on the external system.
265
+
For example, when the processed message causes a semantic error - typically due to a `400 Bad request` - on the external system.
266
266
Outbox messages causing such errors should be removed from the outbox message table before reaching the maximum number of retry attempts and instead application-specific
267
267
counter-measures should be taken to correct the semantic error or ignore the message altogether.
[Learn more about `EventContext.proceed()`.](./event-handlers/#proceed-on){.learn-more}
309
309
310
-
## Troubleshooting
310
+
## Outbox Dead Letter Queue
311
311
312
-
To manually delete entries in the `cds.outbox.Messages` table, you can either
313
-
expose it in a service or programmatically modify it using the `cds.outbox.Messages`
314
-
database entity.
312
+
The transactional outbox tries to process each entry a specific number of times. The number of attempts is configurable per outbox by setting the configuration `cds.outbox.services.<key>.maxAttempts`.
313
+
314
+
[Learn more about CDS Properties.](./developing-applications/properties){.learn-more}
315
+
316
+
Once the maximum number of attempts is exceeded, the corresponding entry is not touched anymore and hence it can be regarded as dead. Dead outbox entries are not deleted automatically. They remain in the database and it's up to the application to take care of the entries. By defining a CDS service, the dead entries can be managed conveniently. Let's have a look, how you can develop a Dead Letter Queue for the transactional outbox.
317
+
318
+
::: warning Changing configuration between deployments
319
+
320
+
It's possible to increase the value of the configuration `cds.outbox.services.<key>.maxAttempts` in between of deployments. Older entries which have reached their max attempts in the past would be retried automatically after deployment of the new microservice version. If the dead letter queue has a large size, this leads to unintended load on the system.
321
+
322
+
:::
323
+
324
+
325
+
### Define the Service
326
+
327
+
::: code-group
328
+
329
+
```cds [srv/outbox-dead-letter-queue-service.cds]
330
+
using from '@sap/cds/srv/outbox';
331
+
332
+
@requires: 'internal-user'
333
+
service OutboxDeadLetterQueueService {
334
+
335
+
@readonly
336
+
entity DeadOutboxMessages as projection on cds.outbox.Messages
337
+
actions {
338
+
action revive();
339
+
action delete();
340
+
};
341
+
342
+
}
343
+
```
344
+
345
+
:::
346
+
347
+
The `OutboxDeadLetterQueueService` provides an entity `DeadOutboxMessages` which is a projection on the outbox table `cds.outbox.Messages` that has two bound actions:
348
+
349
+
-`revive()` sets the number of attempts to `0` such that the outbox entry is going to be processed again.
350
+
-`delete()` deletes the outbox entry from the database.
351
+
352
+
Filters can be applied as for any other CDS defined entity, for example, to filter for a specific outbox where the outbox name is stored in the field `target` of the entity `cds.outbox.Messages`.
353
+
354
+
::: warning `OutboxDeadLetterQueueService` for internal users only
355
+
356
+
It is crucial to make the service `OutboxDeadLetterQueueService` accessible for internal users only as it contains sensitive data that could be exploited for malicious purposes if unauthorized changes are performed.
357
+
358
+
[Learn more about pseudo roles](../guides/security/authorization#pseudo-roles){.learn-more}
359
+
360
+
:::
361
+
362
+
### Filter for Dead Entries
363
+
364
+
This filtering can't be done on the database since the maximum number of attempts is only available from the CDS properties.
365
+
366
+
To ensure that only dead outbox entries are returned when reading `DeadOutboxMessages`, the following code provides the handler for the `DeadLetterQueueService` and the `@After-READ` handler that filters for the dead outbox entries:
The injected `PersistenceService` instance is used to perform the operations on the `Messages` entity since the entity `DeadOutboxMessages` is read-only. Both handlers first retrieve the ID of the entry and then they perform the corresponding operation on the database.
422
+
423
+
[Learn more about CQL statement inspection.](./working-with-cql/query-introspection#cqnanalyzer){.learn-more}
315
424
316
425
::: tip Use paging logic
317
-
Avoid to read all entries of the `cds.outbox.Messages` table at once, as the size of an entry is unpredictable
426
+
Avoid to read all entries of the `cds.outbox.Messages`or `OutboxDeadLetterQueueService.DeadOutboxMessages`table at once, as the size of an entry is unpredictable
318
427
and depends on the size of the payload. Prefer paging logic instead.
319
428
:::
429
+
430
+
## Observability using Open Telemetry
431
+
432
+
The transactional outbox integrates Open Telemetry for logging telemetry data.
433
+
434
+
[Learn more about observability with Open Telemetry.](./operating-applications/observability#open-telemetry){.learn-more}
435
+
436
+
The following KPIs are logged in addition to the spans described in the [observability chapter](./operating-applications/observability):
0 commit comments