committed by
GitHub
9 changed files with 254 additions and 6 deletions
@ -0,0 +1,23 @@ |
|||||
|
/** |
||||
|
* 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 org.thingsboard.server.common.data.audit.AuditLog; |
||||
|
|
||||
|
public interface AuditLogSink { |
||||
|
|
||||
|
void logAction(AuditLog auditLogEntry); |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
/** |
||||
|
* 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 org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.thingsboard.server.common.data.audit.AuditLog; |
||||
|
|
||||
|
@Component |
||||
|
@ConditionalOnProperty(prefix = "audit_log.sink", value = "type", havingValue = "none") |
||||
|
public class DummyAuditLogSink implements AuditLogSink { |
||||
|
|
||||
|
@Override |
||||
|
public void logAction(AuditLog auditLogEntry) { |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,160 @@ |
|||||
|
/** |
||||
|
* 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 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; |
||||
|
@Value("${audit_log.sink.date_format}") |
||||
|
private String dateFormat; |
||||
|
|
||||
|
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(dateFormat); |
||||
|
indexName = indexName.replace(DATE_PLACEHOLDER, now.format(formatter)); |
||||
|
} |
||||
|
return indexName.toLowerCase(); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue