Purpose
Use external API logging when a Chenile service crosses a third-party boundary.
There are two supported cases:
| Case | Meaning | What the developer does |
|---|---|---|
| Inbound external API | A third-party client calls an API exposed by this service | Mark the controller or operation with @ExternalApi |
| Outbound external API | This service calls a third-party HTTP server | Use ChenileExternalClient |
Do not use this for normal Chenile-to-Chenile proxy calls. Chenile proxies are internal service communication. External API logging is for third-party integrations.
Enable Logging
Add the core logging properties in chenile.properties or the service application configuration:
chenile.external-api.logging.enabled=true
chenile.external-api.logging.publisher=pubsub
chenile.external-api.logging.inbound-topic=external.api.inbound
chenile.external-api.logging.outbound-topic=external.api.outbound
chenile.external-api.logging.max-payload-bytes=65536
chenile.external-api.logging.masked-headers=Authorization,x-Authorization
Publisher choices:
| Value | Behavior |
|---|---|
none |
Core creates NoopExternalApiPublisher. It logs that publishing was skipped. |
pubsub |
chenile-pub-sub creates PubSubExternalApiPublisher when that module is on the classpath. |
For Pub/Sub publication, include the Chenile Pub/Sub module and configure the normal Chenile Pub/Sub transport for the service. The external API publisher sends the serialized LogRecord to the inbound or outbound topic.
Mark Inbound External APIs
Use @ExternalApi on the controller class when every operation in the controller is exposed to a third party:
import org.chenile.core.annotation.ExternalApi;
import org.chenile.http.annotation.ChenileController;
import org.chenile.http.handler.ControllerSupport;
import org.springframework.web.bind.annotation.RestController;
@RestController
@ChenileController(value = "orderController", serviceName = "orderService")
@ExternalApi(system = "partner-portal")
public class OrderController extends ControllerSupport {
// operations
}
Use @ExternalApi on a method when only that operation is external, or when you want a specific external operation name:
@PostMapping("/orders")
@ExternalApi(system = "partner-portal", operation = "create-order")
public ResponseEntity<GenericResponse<Order>> createOrder(
HttpServletRequest request, @RequestBody Order order) {
return process("createOrder", request, order);
}
Annotation fields:
| Field | Required | Meaning |
|---|---|---|
system |
Yes | Third-party client or partner system name |
operation |
No | External operation name. If empty, Chenile uses the operation name. |
enabled |
No | Set to false to disable a class-level external marker for a specific operation. |
Method-level annotations override class-level annotations.
Create Outbound Third-Party Calls
Inject ChenileExternalClient into the service or gateway that calls the third-party HTTP system.
import org.chenile.core.external.ChenileExternalClient;
import org.chenile.core.external.ExternalApiRequest;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
@Service
public class PartnerOrderGateway {
private final ChenileExternalClient externalClient;
public PartnerOrderGateway(ChenileExternalClient externalClient) {
this.externalClient = externalClient;
}
public PartnerResponse sendOrder(PartnerOrder order) {
ExternalApiRequest<PartnerResponse> request = ExternalApiRequest.of(
"partner-order-system",
"send-order",
"https://partner.example.com/orders",
HttpMethod.POST,
order,
PartnerResponse.class);
request.headers.put("x-partner-channel", "chenile");
return externalClient.exchange(request);
}
}
ChenileExternalClient uses RestTemplate internally and returns the converted response body. If the third-party server returns an HTTP error or the client fails, the original Spring RestClientException is rethrown after the external API log record is captured.
End-to-End Checklist
- Decide whether the interaction is really third-party inbound or third-party outbound.
- For inbound, annotate only the third-party-facing controller or operation with
@ExternalApi. - For outbound, use
ChenileExternalClient; do not route the call through Chenile proxy. - Configure
chenile.external-api.logging.enabled=true. - Use
publisher=pubsuband provide inbound and outbound topics when records must go to Chenile Pub/Sub. - Configure masked headers for secrets.
- Keep payload logging limits reasonable with
max-payload-bytes. - Verify that normal business requests still succeed if Pub/Sub is unavailable.
What Gets Published
Both inbound and outbound records use org.chenile.core.context.LogRecord.
Important fields:
| Field | Description |
|---|---|
direction |
INBOUND or OUTBOUND |
external |
true |
externalSystem |
Third-party system name |
externalOperation |
External operation name |
requestPayload |
Serialized and truncated request payload |
responsePayload |
Serialized and truncated response payload |
httpStatusCode |
HTTP status when available |
durationMillis |
Measured execution duration |
requestId |
Request ID from headers/context |
correlationId |
Correlation ID, or request ID when correlation ID is absent |
errorCode / errorMessage |
Error details when available |
headers |
Header values with configured masking |
Avoid Duplicate Records
Use these rules to avoid duplicate or noisy Pub/Sub records:
- Mark only externally exposed APIs with
@ExternalApi. - Do not mark internal controllers just because they are HTTP controllers.
- Use
ChenileExternalClientonly for third-party outbound HTTP calls. - Use Chenile proxy for Chenile-to-Chenile service calls.
- Keep inbound and outbound topics separate when consumers need different processing.
Troubleshooting
If no Pub/Sub record appears:
- Confirm
chenile.external-api.logging.enabled=true. - Confirm
chenile.external-api.logging.publisher=pubsub. - Confirm
chenile-pub-subis on the classpath. - Confirm
ChenilePubis configured and available. - Confirm the inbound API has
@ExternalApior the outbound call usesChenileExternalClient. - Confirm the correct topic is configured.
With publisher=none, the no-op publisher writes an info/debug log but does not publish to Pub/Sub.