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
* feat: Implement OpenTelemetry ScimTracer + fix misleading docs (#105)
OpenTelemetryScimTracer auto-configures when OpenTelemetry is on the
classpath and a Tracer bean is available:
- Wraps SCIM operations in spans with attributes
- Records exceptions with StatusCode.ERROR
- Returns trace ID as correlation ID for MDC and events
- Backs off when custom ScimTracer bean provided
- Disabled via scim.tracing.enabled=false
Devil's Advocate fixes:
- HIGH: Nested @configuration for double classpath safety
- MEDIUM: scope.close() before span.end() (correct OTel ordering)
- MEDIUM: Fixed docs reference to non-existent Spring Boot starter
- MEDIUM: Added auto-configuration tests (bean creation + property gate)
Also fixes:
- outbox-pattern.md: Removed false auto-config claim
- README.md: Added scim.idp.claims.* and scim.tracing.enabled properties
- observability.md: Real feature docs replacing pseudocode
Tests: 6 unit + 2 auto-config tests
Closes#105
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: Add OpenTelemetry tracing to fullstack Spring sample
- Add opentelemetry-api, opentelemetry-sdk, opentelemetry-exporter-otlp deps
- Add TracingConfig.java creating OTel Tracer bean with OTLP/gRPC exporter
- Add Jaeger service to docker-compose.yml (observability profile)
- Add OTEL_EXPORTER_OTLP_ENDPOINT env var to backend service
- Update services table with Prometheus, Grafana, and Jaeger rows
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
-**Extensible**: SPI for serialization, HTTP transport, identity, authorization, events
@@ -215,13 +215,16 @@ var publisher = new CompositeEventPublisher(outboundPublisher, auditPublisher);
215
215
216
216
## Observability
217
217
218
-
The SDK provides built-in observability via [Micrometer](https://micrometer.io/) metrics, structured logging, and event correlation.
218
+
The SDK provides built-in observability:
219
219
220
-
See the [Observability Guide](docs/observability.md) for details.
220
+
- **Metrics** — [Micrometer](https://micrometer.io/) auto-configured with Prometheus, Grafana dashboard included
221
+
- **Tracing** — [OpenTelemetry](https://opentelemetry.io/) auto-configured when on classpath (spans, trace IDs, exception recording)
222
+
- **Structured logging** — SLF4J MDC with `scim.correlationId`, `scim.operation`, `scim.resourceType`
223
+
- **Event correlation** — all events carry the tracer's correlation ID
221
224
222
-
### Quick Start (Spring Boot)
225
+
See the [Observability Guide](docs/observability.md) for configuration, metrics reference, and custom implementation examples.
223
226
224
-
Add Micrometer and actuator:
227
+
### Quick Start (Spring Boot)
225
228
226
229
```yaml
227
230
management:
@@ -231,7 +234,7 @@ management:
231
234
include: health,prometheus,metrics
232
235
```
233
236
234
-
Metrics are automatically recorded for all SCIM operations. See the [Spring Boot full-stack sample](scim2-sdk-samples/sample-fullstack-spring/) for a complete example with Prometheus + Grafana.
237
+
Metrics and tracing are automatically recorded for all SCIM operations. See the [Spring Boot full-stack sample](scim2-sdk-samples/sample-fullstack-spring/) for a complete example with Prometheus + Grafana.
235
238
236
239
## Sample Applications
237
240
@@ -278,12 +281,19 @@ All properties are optional with sensible defaults:
278
281
| `scim.persistence.table-name` | `scim_resources` | Database table name for SCIM resource storage |
279
282
| `scim.persistence.schema-name` | *(none)* | Database schema name (e.g., `scim`) — if set, the table is qualified as `schema.table` |
280
283
| `scim.persistence.auto-migrate` | `false` | Run [Flyway](https://flywaydb.org/) migration on startup to create the `scim_resources` table automatically |
284
+
| `scim.tracing.enabled` | `true` | Enable OpenTelemetry tracing auto-configuration. Set to `false` to disable even when OpenTelemetry is on the classpath |
281
285
| `scim.client.base-url` | *(none)* | Base URL of the remote SCIM Service Provider — when set, auto-configures a `ScimClient` bean |
282
286
| `scim.client.connect-timeout` | `10s` | TCP connection timeout for the SCIM client |
283
287
| `scim.client.read-timeout` | `30s` | Read timeout for the SCIM client |
Copy file name to clipboardExpand all lines: docs/observability.md
+36-14Lines changed: 36 additions & 14 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -200,30 +200,52 @@ With Spring Boot, register it as a bean and the auto-configuration will use it i
200
200
fun scimMetrics(): ScimMetrics = DatadogScimMetrics(statsdClient)
201
201
```
202
202
203
+
### OpenTelemetry Tracing
204
+
205
+
The SDK includes `OpenTelemetryScimTracer`, which auto-configures when OpenTelemetry is on the classpath. Add the dependency:
206
+
207
+
```xml
208
+
<dependency>
209
+
<groupId>io.opentelemetry</groupId>
210
+
<artifactId>opentelemetry-api</artifactId>
211
+
</dependency>
212
+
```
213
+
214
+
When an `io.opentelemetry.api.trace.Tracer` bean is available (e.g., via OpenTelemetry's Spring Boot instrumentation or a custom `Tracer` bean), the auto-configuration registers an `OpenTelemetryScimTracer` that:
215
+
216
+
- Wraps every SCIM operation in an OpenTelemetry span with attributes
217
+
- Records exceptions with `StatusCode.ERROR` and `recordException`
218
+
- Provides the current trace ID as the correlation ID for MDC and events
219
+
220
+
No additional configuration is needed. To disable tracing while keeping OpenTelemetry on the classpath:
221
+
222
+
```yaml
223
+
scim:
224
+
tracing:
225
+
enabled: false
226
+
```
227
+
228
+
The `@ConditionalOnMissingBean` guard means you can still provide your own `ScimTracer` bean to override it.
229
+
203
230
### Custom ScimTracer
204
231
205
-
To provide your own tracer (e.g., for OpenTelemetry spans), implement `ScimTracer`:
232
+
To provide a custom tracer implementation, implement the `ScimTracer` interface:
206
233
207
234
```kotlin
208
-
class OpenTelemetryScimTracer(private val tracer: Tracer) : ScimTracer {
235
+
class MyCustomTracer : ScimTracer {
209
236
override fun <T> trace(operationName: String, attributes: Map<String, String>, block: () -> T): T {
210
-
val span = tracer.spanBuilder(operationName).startSpan()
[namastack-outbox](https://github.com/namastack/namastack-outbox) provides transactional outbox support and is available in Maven Central. It integrates with Spring Modulith's event externalization, meaning events stored via the outbox can be automatically forwarded to Kafka, AMQP, or any other supported broker through Spring Modulith's `EventExternalizationConfiguration`.
132
+
> **Note:** This is a documented integration pattern, not a built-in SDK feature.
133
+
134
+
[namastack-outbox](https://github.com/namastack/namastack-outbox) provides transactional outbox support and is available in Maven Central. It integrates with Spring Modulith's event externalization, meaning events stored via the outbox can be forwarded to Kafka, AMQP, or any other supported broker through Spring Modulith's `EventExternalizationConfiguration`.
133
135
134
136
Add the dependency:
135
137
@@ -141,15 +143,29 @@ Add the dependency:
141
143
</dependency>
142
144
```
143
145
144
-
Configure in `application.yml`:
146
+
Implement `ScimEventPublisher` as a Spring `@Component`. The SDK auto-detects it and uses it instead of the default `SpringScimEventPublisher`:
145
147
146
-
```yaml
147
-
scim:
148
-
outbox:
149
-
enabled: true
148
+
```kotlin
149
+
@Component
150
+
classNamastackOutboxAdapter(
151
+
privatevaloutboxService:OutboxService, // from namastack-outbox
152
+
privatevalobjectMapper:ObjectMapper,
153
+
) : ScimEventPublisher {
154
+
overridefunpublish(event:ScimEvent) {
155
+
outboxService.store(
156
+
OutboxEvent(
157
+
id = event.eventId,
158
+
type = event::class.simpleName!!,
159
+
payload = objectMapper.writeValueAsString(event),
160
+
)
161
+
)
162
+
}
163
+
}
150
164
```
151
165
152
-
The SDK auto-configures a `NamastackOutboxAdapter` that delegates to namastack-outbox's event publishing. Events are stored transactionally alongside your resource changes and published asynchronously.
166
+
That's it — Spring auto-configuration picks up your `@Component` automatically via `@ConditionalOnMissingBean(ScimEventPublisher::class)`. Events are then stored transactionally alongside your resource changes and published asynchronously by namastack-outbox's poller.
167
+
168
+
For plain Java (without Spring), wire the publisher manually into `ScimEndpointDispatcher`.
0 commit comments