or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration-properties.mdcore-infrastructure.mddata-integration.mdindex.mdobservability-integration.mdsecurity-integration.mdtesting-support.mdtransport-support.mdweb-integration.md

observability-integration.mddocs/

0

# Observability Integration

1

2

Spring Boot GraphQL Starter provides comprehensive observability features through Spring Boot Actuator, including metrics collection, distributed tracing, and health monitoring for GraphQL applications.

3

4

## Auto-Configuration

5

6

### GraphQL Observability Auto-Configuration

7

8

```java { .api }

9

@AutoConfiguration(after = GraphQlAutoConfiguration.class)

10

@ConditionalOnClass({ GraphQL.class, ObservationRegistry.class })

11

@ConditionalOnBean(GraphQlSource.class)

12

@EnableConfigurationProperties(GraphQlObservationProperties.class)

13

public class GraphQlObservationAutoConfiguration {

14

15

@Bean

16

@ConditionalOnMissingBean

17

public GraphQlObservationInstrumentation graphQlObservationInstrumentation(

18

ObservationRegistry observationRegistry,

19

GraphQlObservationProperties properties

20

) {

21

return new GraphQlObservationInstrumentation(observationRegistry, properties);

22

}

23

}

24

```

25

26

## Metrics Collection

27

28

### Default Metrics

29

30

The starter automatically collects metrics for:

31

32

- **Request Duration**: Time taken to execute GraphQL operations

33

- **Request Count**: Number of GraphQL requests by operation type

34

- **Error Count**: Number of failed GraphQL operations

35

- **Field Resolution Time**: Time taken to resolve individual fields

36

- **Schema Validation**: Schema validation metrics

37

38

### Metrics Configuration

39

40

```java { .api }

41

@ConfigurationProperties("spring.graphql.observation")

42

public class GraphQlObservationProperties {

43

private boolean enabled = true;

44

private Request request = new Request();

45

private Resolver resolver = new Resolver();

46

47

public static class Request {

48

private boolean enabled = true;

49

private Set<String> includedOperationTypes = Set.of("query", "mutation", "subscription");

50

}

51

52

public static class Resolver {

53

private boolean enabled = false; // Disabled by default due to high cardinality

54

private Set<String> includedFields = new HashSet<>();

55

}

56

}

57

```

58

59

### Configuration Examples

60

61

```properties

62

# Enable GraphQL observability

63

spring.graphql.observation.enabled=true

64

65

# Request-level metrics

66

spring.graphql.observation.request.enabled=true

67

spring.graphql.observation.request.included-operation-types=query,mutation

68

69

# Field-level metrics (use cautiously)

70

spring.graphql.observation.resolver.enabled=true

71

spring.graphql.observation.resolver.included-fields=Book.author,User.posts

72

```

73

74

### Custom Metrics

75

76

```java

77

@Component

78

public class GraphQlMetricsCustomizer {

79

80

private final MeterRegistry meterRegistry;

81

private final Counter successfulQueries;

82

private final Timer queryTimer;

83

84

public GraphQlMetricsCustomizer(MeterRegistry meterRegistry) {

85

this.meterRegistry = meterRegistry;

86

this.successfulQueries = Counter.builder("graphql.queries.successful")

87

.description("Number of successful GraphQL queries")

88

.register(meterRegistry);

89

this.queryTimer = Timer.builder("graphql.query.duration")

90

.description("GraphQL query execution time")

91

.register(meterRegistry);

92

}

93

94

@EventListener

95

public void onGraphQlRequestExecuted(GraphQlRequestExecutedEvent event) {

96

if (event.isSuccessful()) {

97

successfulQueries.increment(

98

Tags.of(

99

"operation", event.getOperationType(),

100

"endpoint", event.getEndpoint()

101

)

102

);

103

}

104

105

queryTimer.record(event.getDuration(), TimeUnit.MILLISECONDS,

106

Tags.of("operation", event.getOperationType())

107

);

108

}

109

}

110

```

111

112

## Distributed Tracing

113

114

### Tracing Integration

115

116

```java { .api }

117

@Component

118

public class GraphQlTracingConfigurer implements GraphQlSourceBuilderCustomizer {

119

120

private final Tracer tracer;

121

122

@Override

123

public void customize(GraphQlSource.SchemaResourceBuilder builder) {

124

builder.configureRuntimeWiring(wiringBuilder ->

125

wiringBuilder.fieldVisibility(TracingFieldVisibility.newTracingFieldVisibility()

126

.tracer(tracer)

127

.build())

128

);

129

}

130

}

131

```

132

133

### Custom Tracing

134

135

```java

136

@Component

137

public class CustomGraphQlTracing implements Instrumentation {

138

139

private final Tracer tracer;

140

141

@Override

142

public InstrumentationContext<ExecutionResult> beginExecution(InstrumentationExecutionParameters parameters) {

143

Span span = tracer.nextSpan()

144

.name("graphql.execution")

145

.tag("graphql.operation.type", getOperationType(parameters))

146

.tag("graphql.operation.name", getOperationName(parameters))

147

.start();

148

149

return new SimpleInstrumentationContext<ExecutionResult>() {

150

@Override

151

public void onCompleted(ExecutionResult result, Throwable t) {

152

if (t != null) {

153

span.tag("error", t.getMessage());

154

}

155

span.tag("graphql.errors.count", String.valueOf(result.getErrors().size()));

156

span.end();

157

}

158

};

159

}

160

161

@Override

162

public InstrumentationContext<Object> beginFieldFetch(InstrumentationFieldFetchParameters parameters) {

163

String fieldName = parameters.getField().getName();

164

String typeName = parameters.getFieldDefinition().getType().toString();

165

166

Span span = tracer.nextSpan()

167

.name("graphql.field.fetch")

168

.tag("graphql.field.name", fieldName)

169

.tag("graphql.field.type", typeName)

170

.start();

171

172

return SimpleInstrumentationContext.noOp(); // Simplified for example

173

}

174

}

175

```

176

177

## Health Monitoring

178

179

### GraphQL Health Indicator

180

181

```java { .api }

182

@Component

183

public class GraphQlHealthIndicator implements HealthIndicator {

184

185

private final GraphQlSource graphQlSource;

186

private final ExecutionGraphQlService executionService;

187

188

@Override

189

public Health health() {

190

try {

191

// Perform basic health check query

192

String healthQuery = "{ __schema { types { name } } }";

193

194

WebGraphQlRequest request = WebGraphQlRequest.builder()

195

.query(healthQuery)

196

.build();

197

198

Mono<WebGraphQlResponse> response = executionService.execute(request);

199

WebGraphQlResponse result = response.block(Duration.ofSeconds(5));

200

201

if (result != null && result.getErrors().isEmpty()) {

202

return Health.up()

203

.withDetail("schema.types", result.getData())

204

.withDetail("endpoint", "/graphql")

205

.build();

206

} else {

207

return Health.down()

208

.withDetail("errors", result != null ? result.getErrors() : "Timeout")

209

.build();

210

}

211

} catch (Exception e) {

212

return Health.down()

213

.withDetail("error", e.getMessage())

214

.withException(e)

215

.build();

216

}

217

}

218

}

219

```

220

221

### Schema Health Checks

222

223

```java

224

@Component

225

public class GraphQlSchemaHealthIndicator implements HealthIndicator {

226

227

private final GraphQlSource graphQlSource;

228

229

@Override

230

public Health health() {

231

try {

232

GraphQLSchema schema = graphQlSource.schema();

233

234

Health.Builder builder = Health.up();

235

236

// Check schema basics

237

builder.withDetail("schema.query.type", schema.getQueryType().getName());

238

239

if (schema.getMutationType() != null) {

240

builder.withDetail("schema.mutation.type", schema.getMutationType().getName());

241

}

242

243

if (schema.getSubscriptionType() != null) {

244

builder.withDetail("schema.subscription.type", schema.getSubscriptionType().getName());

245

}

246

247

// Count types and fields

248

int typeCount = schema.getAllTypesAsList().size();

249

builder.withDetail("schema.types.count", typeCount);

250

251

return builder.build();

252

253

} catch (Exception e) {

254

return Health.down()

255

.withDetail("error", "Schema validation failed")

256

.withException(e)

257

.build();

258

}

259

}

260

}

261

```

262

263

## Actuator Endpoints

264

265

### GraphQL Info Endpoint

266

267

```java { .api }

268

@Component

269

public class GraphQlInfoContributor implements InfoContributor {

270

271

private final GraphQlSource graphQlSource;

272

private final GraphQlProperties properties;

273

274

@Override

275

public void contribute(Info.Builder builder) {

276

try {

277

GraphQLSchema schema = graphQlSource.schema();

278

279

Map<String, Object> graphqlInfo = new HashMap<>();

280

graphqlInfo.put("endpoint", properties.getHttp().getPath());

281

graphqlInfo.put("graphiql.enabled", properties.getGraphiql().isEnabled());

282

graphqlInfo.put("introspection.enabled", properties.getSchema().getIntrospection().isEnabled());

283

284

// Schema information

285

Map<String, Object> schemaInfo = new HashMap<>();

286

schemaInfo.put("types.count", schema.getAllTypesAsList().size());

287

schemaInfo.put("query.fields", schema.getQueryType().getFieldDefinitions().size());

288

289

if (schema.getMutationType() != null) {

290

schemaInfo.put("mutation.fields", schema.getMutationType().getFieldDefinitions().size());

291

}

292

293

if (schema.getSubscriptionType() != null) {

294

schemaInfo.put("subscription.fields", schema.getSubscriptionType().getFieldDefinitions().size());

295

}

296

297

graphqlInfo.put("schema", schemaInfo);

298

builder.withDetail("graphql", graphqlInfo);

299

300

} catch (Exception e) {

301

builder.withDetail("graphql", Map.of("error", e.getMessage()));

302

}

303

}

304

}

305

```

306

307

### Custom Actuator Endpoint

308

309

```java { .api }

310

@Component

311

@Endpoint(id = "graphql")

312

public class GraphQlActuatorEndpoint {

313

314

private final GraphQlSource graphQlSource;

315

private final MeterRegistry meterRegistry;

316

317

@ReadOperation

318

public Map<String, Object> graphql() {

319

Map<String, Object> result = new HashMap<>();

320

321

// Schema information

322

result.put("schema", getSchemaInfo());

323

324

// Metrics

325

result.put("metrics", getMetricsInfo());

326

327

// Health status

328

result.put("health", getHealthInfo());

329

330

return result;

331

}

332

333

@ReadOperation

334

public Map<String, Object> schema() {

335

return getSchemaInfo();

336

}

337

338

@ReadOperation

339

public String schemaSdl() {

340

SchemaPrinter printer = new SchemaPrinter();

341

return printer.print(graphQlSource.schema());

342

}

343

344

private Map<String, Object> getSchemaInfo() {

345

GraphQLSchema schema = graphQlSource.schema();

346

Map<String, Object> info = new HashMap<>();

347

348

info.put("types", schema.getAllTypesAsList().stream()

349

.collect(Collectors.groupingBy(

350

GraphQLType::getClass,

351

Collectors.counting()

352

)));

353

354

return info;

355

}

356

357

private Map<String, Object> getMetricsInfo() {

358

Map<String, Object> metrics = new HashMap<>();

359

360

// Collect GraphQL-related metrics

361

meterRegistry.getMeters().stream()

362

.filter(meter -> meter.getId().getName().startsWith("graphql"))

363

.forEach(meter -> {

364

if (meter instanceof Counter) {

365

metrics.put(meter.getId().getName(), ((Counter) meter).count());

366

} else if (meter instanceof Timer) {

367

Timer timer = (Timer) meter;

368

metrics.put(meter.getId().getName(), Map.of(

369

"count", timer.count(),

370

"totalTime", timer.totalTime(TimeUnit.MILLISECONDS),

371

"mean", timer.mean(TimeUnit.MILLISECONDS)

372

));

373

}

374

});

375

376

return metrics;

377

}

378

}

379

```

380

381

## Logging Integration

382

383

### Structured Logging

384

385

```java

386

@Component

387

public class GraphQlLoggingInstrumentation implements Instrumentation {

388

389

private static final Logger log = LoggerFactory.getLogger(GraphQlLoggingInstrumentation.class);

390

391

@Override

392

public InstrumentationContext<ExecutionResult> beginExecution(InstrumentationExecutionParameters parameters) {

393

String operationType = getOperationType(parameters);

394

String operationName = getOperationName(parameters);

395

396

log.info("GraphQL execution started: type={}, name={}", operationType, operationName);

397

398

long startTime = System.currentTimeMillis();

399

400

return new SimpleInstrumentationContext<ExecutionResult>() {

401

@Override

402

public void onCompleted(ExecutionResult result, Throwable t) {

403

long duration = System.currentTimeMillis() - startTime;

404

405

if (t != null) {

406

log.error("GraphQL execution failed: type={}, name={}, duration={}ms, error={}",

407

operationType, operationName, duration, t.getMessage(), t);

408

} else {

409

log.info("GraphQL execution completed: type={}, name={}, duration={}ms, errors={}",

410

operationType, operationName, duration, result.getErrors().size());

411

}

412

}

413

};

414

}

415

}

416

```

417

418

### Request/Response Logging

419

420

```java

421

@Component

422

public class GraphQlRequestLoggingInterceptor implements WebGraphQlInterceptor {

423

424

private static final Logger log = LoggerFactory.getLogger(GraphQlRequestLoggingInterceptor.class);

425

426

@Override

427

public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, WebGraphQlInterceptorChain chain) {

428

String requestId = UUID.randomUUID().toString();

429

430

log.debug("GraphQL request [{}]: query={}, variables={}",

431

requestId, request.getDocument(), request.getVariables());

432

433

return chain.next(request)

434

.doOnNext(response -> {

435

log.debug("GraphQL response [{}]: data present={}, errors={}",

436

requestId, response.getData() != null, response.getErrors().size());

437

})

438

.doOnError(error -> {

439

log.error("GraphQL request [{}] failed: {}", requestId, error.getMessage(), error);

440

});

441

}

442

}

443

```

444

445

## Configuration Properties

446

447

```properties

448

# Observability configuration

449

management.endpoints.web.exposure.include=health,info,metrics,graphql

450

management.endpoint.graphql.enabled=true

451

management.metrics.export.prometheus.enabled=true

452

453

# GraphQL-specific observability

454

spring.graphql.observation.enabled=true

455

spring.graphql.observation.request.enabled=true

456

457

# Logging levels

458

logging.level.org.springframework.graphql=DEBUG

459

logging.level.graphql.execution=TRACE

460

```