ElasticsearchAuditLogSink.java
Home
/
dao /
src /
main /
java /
org /
thingsboard /
server /
dao /
audit /
sink /
ElasticsearchAuditLogSink.java
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.audit.sink;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.nio.entity.NStringEntity;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.ResponseListener;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.audit.AuditLog;
import org.thingsboard.server.common.data.id.TenantId;
import javax.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
@Component
@ConditionalOnProperty(prefix = "audit_log.sink", value = "type", havingValue = "elasticsearch")
@Slf4j
public class ElasticsearchAuditLogSink implements AuditLogSink {
private static final String TENANT_PLACEHOLDER = "@{TENANT}";
private static final String DATE_PLACEHOLDER = "@{DATE}";
private static final String DATE_FORMAT = "YYYY.MM.dd";
private static final String INDEX_TYPE = "audit_log";
private final ObjectMapper mapper = new ObjectMapper();
@Value("${audit_log.sink.index_pattern}")
private String indexPattern;
@Value("${audit_log.sink.scheme_name}")
private String schemeName;
@Value("${audit_log.sink.host}")
private String host;
@Value("${audit_log.sink.port}")
private int port;
@Value("${audit_log.sink.user_name}")
private String userName;
@Value("${audit_log.sink.password}")
private String password;
private RestClient restClient;
@PostConstruct
public void init() {
try {
log.trace("Adding elastic rest endpoint... host [{}], port [{}], scheme name [{}]",
host, port, schemeName);
RestClientBuilder builder = RestClient.builder(
new HttpHost(host, port, schemeName));
if (StringUtils.isNotEmpty(userName) &&
StringUtils.isNotEmpty(password)) {
log.trace("...using username [{}] and password ***", userName);
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(userName, password));
builder.setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider));
}
this.restClient = builder.build();
} catch (Exception e) {
log.error("Sink init failed!", e);
throw new RuntimeException(e.getMessage(), e);
}
}
@Override
public void logAction(AuditLog auditLogEntry) {
String jsonContent = createElasticJsonRecord(auditLogEntry);
HttpEntity entity = new NStringEntity(
jsonContent,
ContentType.APPLICATION_JSON);
restClient.performRequestAsync(
HttpMethod.POST.name(),
String.format("/%s/%s", getIndexName(auditLogEntry.getTenantId()), INDEX_TYPE),
Collections.emptyMap(),
entity,
responseListener);
}
private String createElasticJsonRecord(AuditLog auditLog) {
ObjectNode auditLogNode = mapper.createObjectNode();
auditLogNode.put("postDate", LocalDateTime.now().toString());
auditLogNode.put("id", auditLog.getId().getId().toString());
auditLogNode.put("entityName", auditLog.getEntityName());
auditLogNode.put("tenantId", auditLog.getTenantId().getId().toString());
if (auditLog.getCustomerId() != null) {
auditLogNode.put("customerId", auditLog.getCustomerId().getId().toString());
}
auditLogNode.put("entityId", auditLog.getEntityId().getId().toString());
auditLogNode.put("entityType", auditLog.getEntityId().getEntityType().name());
auditLogNode.put("userId", auditLog.getUserId().getId().toString());
auditLogNode.put("userName", auditLog.getUserName());
auditLogNode.put("actionType", auditLog.getActionType().name());
if (auditLog.getActionData() != null) {
auditLogNode.put("actionData", auditLog.getActionData().toString());
}
auditLogNode.put("actionStatus", auditLog.getActionStatus().name());
auditLogNode.put("actionFailureDetails", auditLog.getActionFailureDetails());
return auditLogNode.toString();
}
private ResponseListener responseListener = new ResponseListener() {
@Override
public void onSuccess(Response response) {
log.trace("Elasticsearch sink log action method succeeded. Response result [{}]!", response);
}
@Override
public void onFailure(Exception exception) {
log.warn("Elasticsearch sink log action method failed!", exception);
}
};
private String getIndexName(TenantId tenantId) {
String indexName = indexPattern;
if (indexName.contains(TENANT_PLACEHOLDER) && tenantId != null) {
indexName = indexName.replace(TENANT_PLACEHOLDER, tenantId.getId().toString());
}
if (indexName.contains(DATE_PLACEHOLDER)) {
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
indexName = indexName.replace(DATE_PLACEHOLDER, now.format(formatter));
}
return indexName.toLowerCase();
}
}