diff --git a/README.md b/README.md index 7dccf18..9042d07 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ API client library for Kintone REST APIs on Java. Add dependency declaration in `build.gradle` of your project. ``` dependencies { - implementation 'com.kintone:kintone-java-client:1.4.0' + implementation 'com.kintone:kintone-java-client:2.0.0' } ``` - For projects using Maven @@ -17,7 +17,7 @@ API client library for Kintone REST APIs on Java. com.kintone kintone-java-client - 1.4.0 + 2.0.0 ``` diff --git a/build.gradle b/build.gradle index 2dd226f..3e3e007 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { id 'com.github.hierynomus.license' version '0.16.1' } -version = '1.4.1' +version = '2.0.0' sourceCompatibility = 1.8 targetCompatibility = 1.8 @@ -27,6 +27,7 @@ dependencies { implementation 'org.apache.httpcomponents.client5:httpclient5:5.3.1' implementation 'com.fasterxml.jackson.core:jackson-databind:2.16.1' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.16.1' + implementation 'org.slf4j:slf4j-api:1.7.36' compileOnly 'org.projectlombok:lombok:1.18.30' annotationProcessor 'org.projectlombok:lombok:1.18.30' diff --git a/docs/getting-started.md b/docs/getting-started.md index 18ba1e2..06fa277 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -29,7 +29,7 @@ client.close(); Add dependency declaration in `build.gradle` of your project. ```groovy dependencies { - implementation 'com.kintone:kintone-java-client:1.4.0' + implementation 'com.kintone:kintone-java-client:2.0.0' } ``` @@ -39,7 +39,7 @@ Add dependency declaration in `pom.xml` of your project. com.kintone kintone-java-client - 1.4.0 + 2.0.0 ``` @@ -152,6 +152,33 @@ Record newRecord = new Record().putField("text", new SingleLineTextFieldValue(va clientB.record().addRecord(app2, newRecord); ``` +#### Debug Logging + +Kintone Java Client outputs the contents of requests as debug logs through the logging interface +provided by the [Simple Logging Facade for Java (SLF4J)](https://slf4j.org/) package. +Therefore, you can check the logs by enabling the logger according to the configuration instructions +of the logging implementation you are using. The logger name is `com.kintone.client.requestLog`. + +For example, if you are using [Logback](https://logback.qos.ch/) as your logging backend, +the configuration file (`logback.xml`) would look like follows: + +``` + + + + %d %logger %msg%n + + + + + + + + + + +``` + ### Record Operations `KintoneClient` supports record related operations through the `RecordClient` subcomponent. diff --git a/src/main/java/com/kintone/client/InternalClientImpl.java b/src/main/java/com/kintone/client/InternalClientImpl.java index def55c9..be0f7c9 100644 --- a/src/main/java/com/kintone/client/InternalClientImpl.java +++ b/src/main/java/com/kintone/client/InternalClientImpl.java @@ -14,6 +14,7 @@ import com.kintone.client.model.BulkRequestContent; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -41,13 +42,21 @@ import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder; -import org.apache.hc.core5.http.*; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.ParseException; import org.apache.hc.core5.http.io.entity.ByteArrayEntity; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.ssl.TLS; import org.apache.hc.core5.pool.PoolConcurrencyPolicy; import org.apache.hc.core5.pool.PoolReusePolicy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; class InternalClientImpl extends InternalClient { @@ -61,6 +70,9 @@ class InternalClientImpl extends InternalClient { private final HttpHost proxyHost; private final BasicScheme proxyAuth; + private static final String REQUEST_LOGGER_NAME = "com.kintone.client.requestLog"; + private final Logger logger = LoggerFactory.getLogger(REQUEST_LOGGER_NAME); + InternalClientImpl( String baseUrl, Auth auth, @@ -152,6 +164,20 @@ private HttpContext createHttpContext() { return context; } + private String readInputStream(InputStream in) { + try (InputStreamReader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) { + StringBuilder sb = new StringBuilder(); + char[] buffer = new char[1024]; + int size; + while ((size = reader.read(buffer)) > 0) { + sb.append(buffer, 0, size); + } + return sb.toString(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + @Override T call( KintoneApi api, KintoneRequest body, List handlers) { @@ -168,6 +194,11 @@ T call( KintoneRequest body, Class clazz, List handlers) { + if (logger.isDebugEnabled()) { + String json = mapper.formatString(body); + logger.debug("request: {} {} {}", method, path, json); + } + HttpUriRequest request = createJsonRequest(method, path, body); try { return httpClient.execute( @@ -215,12 +246,19 @@ private List parseBulkRequestResponse( return resultBodies; } + @SuppressWarnings("unchecked") @Override BulkRequestsResponseBody bulkRequest(BulkRequestsRequest body, List handlers) { + KintoneHttpMethod method = KintoneApi.BULK_REQUESTS.getMethod(); String path = getApiPath(KintoneApi.BULK_REQUESTS); + Map bulkRequestBody = createBulkRequestBody(body); - HttpUriRequest request = - createJsonRequest(KintoneApi.BULK_REQUESTS.getMethod(), path, createBulkRequestBody(body)); + if (logger.isDebugEnabled()) { + String json = mapper.formatString(bulkRequestBody); + logger.debug("request: {} {} {}", method, path, json); + } + + HttpUriRequest request = createJsonRequest(method, path, bulkRequestBody); try { return httpClient.execute( request, @@ -230,9 +268,17 @@ BulkRequestsResponseBody bulkRequest(BulkRequestsRequest body, List { - @SuppressWarnings("unchecked") - Map responseMap = mapper.parse(stream, Map.class); - @SuppressWarnings("unchecked") + Map responseMap; + if (logger.isDebugEnabled()) { + String responseBody = readInputStream(stream); + logger.debug( + "response status: {}, response body: {}", + response.getCode(), + responseBody); + responseMap = mapper.parseString(responseBody, Map.class); + } else { + responseMap = mapper.parse(stream, Map.class); + } List results = (List) responseMap.get("results"); List bodies = parseBulkRequestResponse(body.getRequests(), results); @@ -248,11 +294,19 @@ BulkRequestsResponseBody bulkRequest(BulkRequestsRequest body, List handlers) { + KintoneHttpMethod method = KintoneApi.DOWNLOAD_FILE.getMethod(); String path = getApiPath(KintoneApi.DOWNLOAD_FILE); - HttpUriRequest req = createJsonRequest(KintoneApi.DOWNLOAD_FILE.getMethod(), path, request); + + if (logger.isDebugEnabled()) { + String json = mapper.formatString(request); + logger.debug("request: {} {} {}", method, path, json); + } + + HttpUriRequest req = createJsonRequest(method, path, request); KintoneResponse r; try { ClassicHttpResponse response = httpClient.executeOpen(null, req, createHttpContext()); + logger.debug("response status: {}", response.getCode()); com.kintone.client.model.HttpResponse resp = new HttpResponseImpl(response); r = parseResponse(response, stream -> new DownloadFileResponseBody(resp)); } catch (IOException e) { @@ -281,9 +335,12 @@ KintoneResponse upload( "file", new InputStreamBody(content, ContentType.create(contentType), filename)); String headerContentType = "multipart/form-data; boundary=" + boundary; + KintoneHttpMethod method = KintoneApi.UPLOAD_FILE.getMethod(); String path = getApiPath(KintoneApi.UPLOAD_FILE); - HttpUriRequest httpRequest = - createRequest(KintoneApi.UPLOAD_FILE.getMethod(), path, headerContentType, builder.build()); + + logger.debug("request: {} {}, file: {}", method, path, filename); + + HttpUriRequest httpRequest = createRequest(method, path, headerContentType, builder.build()); try { return httpClient.execute( httpRequest, @@ -314,6 +371,7 @@ private KintoneResponse parseResponse( result = converter.apply(response.getEntity().getContent()); } else { errorBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); + logger.debug("response status: {}, response body: {}", statusCode, errorBody); } } catch (IOException | ParseException e) { throw new KintoneRuntimeException("Failed to request", e); @@ -323,7 +381,17 @@ private KintoneResponse parseResponse( private KintoneResponse parseJsonResponse( ClassicHttpResponse response, Class responseClass) { - return parseResponse(response, stream -> mapper.parse(stream, responseClass)); + return parseResponse( + response, + stream -> { + if (logger.isDebugEnabled()) { + String body = readInputStream(stream); + logger.debug("response status: {}, response body: {}", response.getCode(), body); + return mapper.parseString(body, responseClass); + } else { + return mapper.parse(stream, responseClass); + } + }); } private void applyHandlers(KintoneResponse response, List handlers) { diff --git a/src/main/java/com/kintone/client/JsonMapper.java b/src/main/java/com/kintone/client/JsonMapper.java index dba4d88..d9f1c6f 100644 --- a/src/main/java/com/kintone/client/JsonMapper.java +++ b/src/main/java/com/kintone/client/JsonMapper.java @@ -51,6 +51,14 @@ byte[] format(Object obj) { } } + String formatString(Object obj) { + try { + return mapper.writeValueAsString(obj); + } catch (JsonProcessingException e) { + throw new KintoneRuntimeException("Failed to format request JSON", e); + } + } + T parse(InputStream stream, Class clazz) { try { return mapper.readValue(stream, clazz); @@ -59,6 +67,14 @@ T parse(InputStream stream, Class clazz) { } } + T parseString(String input, Class clazz) { + try { + return mapper.readValue(input, clazz); + } catch (IOException e) { + throw new KintoneRuntimeException("Failed to parse response JSON", e); + } + } + T convert(Object object, Class clazz) { return mapper.convertValue(object, clazz); }