使用 403 写入 Azure 的 Log Analytics 数据收集器 API return。昨天工作正常
Writes to Azure's Log Analytics Data Collector API return with 403. Worked fine yesterday
以下代码使用 com.google.code.gson.gson:2.8.5
和 org.asynchttpclient.async-http-client:2.5.2
将 JSON 发送到 Azure 的 Log Analytics。直到昨天午夜它都工作正常,但突然开始返回 HTTP 403 响应。出了什么问题?
public class LogAnalyticsSender {
private static final Charset UTF8 = Charset.forName("UTF-8");
private static final String HMAC_SHA256_ALG = "HmacSHA256";
static String createAuthorization(String workspaceId, String key, int contentLength, String rfc1123Date) {
try {
// Documentation: https://docs.microsoft.com/en-us/rest/api/loganalytics/create-request
String signature = String.format("POST\n%d\napplication/json\nx-ms-date:%s\n/api/logs", contentLength, rfc1123Date);
Mac mac = Mac.getInstance(HMAC_SHA256_ALG);
mac.init(new SecretKeySpec(DatatypeConverter.parseBase64Binary(key), HMAC_SHA256_ALG));
String hmac = DatatypeConverter.printBase64Binary(mac.doFinal(signature.getBytes(UTF8)));
return String.format("SharedKey %s:%s", workspaceId, hmac);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException(e);
}
}
final SslEngineFactory defaultSslEngineFactory = (configuration, peerHost, peerPort) -> {
try {
SSLContext sslCtx = SSLContext.getDefault();
SSLEngine sslEngine = sslCtx.createSSLEngine(peerHost, peerPort);
sslEngine.setUseClientMode(true);
return sslEngine;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
};
final String key;
final String workspace;
final Gson gson;
final DefaultAsyncHttpClient httpClient;
public LogAnalyticsSender(String workspaceId, String base64Key, int maxConnections) {
DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder().setMaxConnections(maxConnections)
.setThreadPoolName("LogAnalyticsSender").setSslEngineFactory(this.defaultSslEngineFactory).build();
this.key = base64Key;
this.workspace = workspaceId;
this.gson = new GsonBuilder().create();
this.httpClient = new DefaultAsyncHttpClient(config);
}
public CompletableFuture<Response> sendPojo(Object o, String logType) {
String json = this.gson.toJson(o);
return sendRawJson(json, logType);
}
public CompletableFuture<Response> sendPojo(JsonElement element, String logType) {
String json = this.gson.toJson(element);
return sendRawJson(json, logType);
}
public CompletableFuture<Response> sendRawJson(String rawJson, String logType) {
int bodyLength = rawJson.getBytes(UTF8).length;
String nowRfc1123 = DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC));
String createAuthorization = createAuthorization(this.workspace, this.key, bodyLength, nowRfc1123);
return this.httpClient.preparePost("https://" + this.workspace + ".ods.opinsights.azure.com/api/logs?api-version=2016-04-01").setBody(rawJson)
.addHeader("Authorization", createAuthorization).addHeader("Content-Type", "application/json").addHeader("Log-Type", logType)
.addHeader("x-ms-date", nowRfc1123).execute().toCompletableFuture();
}
public void shutdown() {
this.httpClient.close();
}
}
(回答我自己的问题)
当日期从 7 月 31 日切换到 8 月 1 日时出现问题。原来 Java 的 DateTimeFormatter.RFC_1123_DATE_TIME
将日期写为单个数字,并且日志分析 API 不喜欢那样。
解决方案是将常规 RFC 1123 DateTimeFormatter
替换为使用两位数字的模式:
DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss O")
以下代码使用 com.google.code.gson.gson:2.8.5
和 org.asynchttpclient.async-http-client:2.5.2
将 JSON 发送到 Azure 的 Log Analytics。直到昨天午夜它都工作正常,但突然开始返回 HTTP 403 响应。出了什么问题?
public class LogAnalyticsSender {
private static final Charset UTF8 = Charset.forName("UTF-8");
private static final String HMAC_SHA256_ALG = "HmacSHA256";
static String createAuthorization(String workspaceId, String key, int contentLength, String rfc1123Date) {
try {
// Documentation: https://docs.microsoft.com/en-us/rest/api/loganalytics/create-request
String signature = String.format("POST\n%d\napplication/json\nx-ms-date:%s\n/api/logs", contentLength, rfc1123Date);
Mac mac = Mac.getInstance(HMAC_SHA256_ALG);
mac.init(new SecretKeySpec(DatatypeConverter.parseBase64Binary(key), HMAC_SHA256_ALG));
String hmac = DatatypeConverter.printBase64Binary(mac.doFinal(signature.getBytes(UTF8)));
return String.format("SharedKey %s:%s", workspaceId, hmac);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException(e);
}
}
final SslEngineFactory defaultSslEngineFactory = (configuration, peerHost, peerPort) -> {
try {
SSLContext sslCtx = SSLContext.getDefault();
SSLEngine sslEngine = sslCtx.createSSLEngine(peerHost, peerPort);
sslEngine.setUseClientMode(true);
return sslEngine;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
};
final String key;
final String workspace;
final Gson gson;
final DefaultAsyncHttpClient httpClient;
public LogAnalyticsSender(String workspaceId, String base64Key, int maxConnections) {
DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder().setMaxConnections(maxConnections)
.setThreadPoolName("LogAnalyticsSender").setSslEngineFactory(this.defaultSslEngineFactory).build();
this.key = base64Key;
this.workspace = workspaceId;
this.gson = new GsonBuilder().create();
this.httpClient = new DefaultAsyncHttpClient(config);
}
public CompletableFuture<Response> sendPojo(Object o, String logType) {
String json = this.gson.toJson(o);
return sendRawJson(json, logType);
}
public CompletableFuture<Response> sendPojo(JsonElement element, String logType) {
String json = this.gson.toJson(element);
return sendRawJson(json, logType);
}
public CompletableFuture<Response> sendRawJson(String rawJson, String logType) {
int bodyLength = rawJson.getBytes(UTF8).length;
String nowRfc1123 = DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC));
String createAuthorization = createAuthorization(this.workspace, this.key, bodyLength, nowRfc1123);
return this.httpClient.preparePost("https://" + this.workspace + ".ods.opinsights.azure.com/api/logs?api-version=2016-04-01").setBody(rawJson)
.addHeader("Authorization", createAuthorization).addHeader("Content-Type", "application/json").addHeader("Log-Type", logType)
.addHeader("x-ms-date", nowRfc1123).execute().toCompletableFuture();
}
public void shutdown() {
this.httpClient.close();
}
}
(回答我自己的问题)
当日期从 7 月 31 日切换到 8 月 1 日时出现问题。原来 Java 的 DateTimeFormatter.RFC_1123_DATE_TIME
将日期写为单个数字,并且日志分析 API 不喜欢那样。
解决方案是将常规 RFC 1123 DateTimeFormatter
替换为使用两位数字的模式:
DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss O")