10 changed files with 479 additions and 112 deletions
@ -0,0 +1,173 @@ |
|||
/** |
|||
* Copyright © 2016-2018 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.service.transport; |
|||
|
|||
import com.fasterxml.jackson.core.JsonProcessingException; |
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import com.google.common.util.concurrent.Futures; |
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.id.DeviceId; |
|||
import org.thingsboard.server.common.data.relation.EntityRelation; |
|||
import org.thingsboard.server.common.data.security.DeviceCredentials; |
|||
import org.thingsboard.server.common.data.security.DeviceCredentialsType; |
|||
import org.thingsboard.server.dao.device.DeviceCredentialsService; |
|||
import org.thingsboard.server.dao.device.DeviceService; |
|||
import org.thingsboard.server.dao.relation.RelationService; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; |
|||
import org.thingsboard.server.kafka.TBKafkaConsumerTemplate; |
|||
import org.thingsboard.server.kafka.TBKafkaProducerTemplate; |
|||
import org.thingsboard.server.kafka.TbKafkaResponseTemplate; |
|||
import org.thingsboard.server.kafka.TbKafkaSettings; |
|||
import org.thingsboard.server.service.cluster.discovery.DiscoveryService; |
|||
import org.thingsboard.server.service.executors.DbCallbackExecutorService; |
|||
import org.thingsboard.server.service.state.DeviceStateService; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import javax.annotation.PreDestroy; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.concurrent.locks.ReentrantLock; |
|||
|
|||
/** |
|||
* Created by ashvayka on 05.10.18. |
|||
*/ |
|||
@Slf4j |
|||
@Service |
|||
public class LocalTransportApiService implements TransportApiService { |
|||
|
|||
private static final ObjectMapper mapper = new ObjectMapper(); |
|||
|
|||
@Autowired |
|||
private DeviceService deviceService; |
|||
|
|||
@Autowired |
|||
private RelationService relationService; |
|||
|
|||
@Autowired |
|||
private DeviceCredentialsService deviceCredentialsService; |
|||
|
|||
@Autowired |
|||
private DeviceStateService deviceStateService; |
|||
|
|||
@Autowired |
|||
private DbCallbackExecutorService dbCallbackExecutorService; |
|||
|
|||
private ReentrantLock deviceCreationLock = new ReentrantLock(); |
|||
|
|||
@Override |
|||
public ListenableFuture<TransportApiResponseMsg> handle(TransportApiRequestMsg transportApiRequestMsg) { |
|||
if (transportApiRequestMsg.hasValidateTokenRequestMsg()) { |
|||
ValidateDeviceTokenRequestMsg msg = transportApiRequestMsg.getValidateTokenRequestMsg(); |
|||
return validateCredentials(msg.getToken(), DeviceCredentialsType.ACCESS_TOKEN); |
|||
} else if (transportApiRequestMsg.hasValidateX509CertRequestMsg()) { |
|||
ValidateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getValidateX509CertRequestMsg(); |
|||
return validateCredentials(msg.getHash(), DeviceCredentialsType.X509_CERTIFICATE); |
|||
} else if (transportApiRequestMsg.hasGetOrCreateDeviceRequestMsg()) { |
|||
return handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg()); |
|||
} |
|||
return getEmptyTransportApiResponseFuture(); |
|||
} |
|||
|
|||
private ListenableFuture<TransportApiResponseMsg> validateCredentials(String credentialsId, DeviceCredentialsType credentialsType) { |
|||
//TODO: Make async and enable caching
|
|||
DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(credentialsId); |
|||
if (credentials != null && credentials.getCredentialsType() == credentialsType) { |
|||
return getDeviceInfo(credentials.getDeviceId()); |
|||
} else { |
|||
return getEmptyTransportApiResponseFuture(); |
|||
} |
|||
} |
|||
|
|||
private ListenableFuture<TransportApiResponseMsg> handle(GetOrCreateDeviceFromGatewayRequestMsg requestMsg) { |
|||
DeviceId gatewayId = new DeviceId(new UUID(requestMsg.getGatewayIdMSB(), requestMsg.getGatewayIdLSB())); |
|||
ListenableFuture<Device> gatewayFuture = deviceService.findDeviceByIdAsync(gatewayId); |
|||
return Futures.transform(gatewayFuture, gateway -> { |
|||
deviceCreationLock.lock(); |
|||
try { |
|||
Device device = deviceService.findDeviceByTenantIdAndName(gateway.getTenantId(), requestMsg.getDeviceName()); |
|||
if (device == null) { |
|||
device = new Device(); |
|||
device.setTenantId(gateway.getTenantId()); |
|||
device.setName(requestMsg.getDeviceName()); |
|||
device.setType(requestMsg.getDeviceType()); |
|||
device.setCustomerId(gateway.getCustomerId()); |
|||
device = deviceService.saveDevice(device); |
|||
relationService.saveRelationAsync(new EntityRelation(gateway.getId(), device.getId(), "Created")); |
|||
deviceStateService.onDeviceAdded(device); |
|||
} |
|||
return TransportApiResponseMsg.newBuilder() |
|||
.setGetOrCreateDeviceResponseMsg(GetOrCreateDeviceFromGatewayResponseMsg.newBuilder().setDeviceInfo(getDeviceInfoProto(device)).build()).build(); |
|||
} catch (JsonProcessingException e) { |
|||
log.warn("[{}] Failed to lookup device by gateway id and name", gatewayId, requestMsg.getDeviceName(), e); |
|||
throw new RuntimeException(e); |
|||
} finally { |
|||
deviceCreationLock.unlock(); |
|||
} |
|||
}, dbCallbackExecutorService); |
|||
} |
|||
|
|||
|
|||
private ListenableFuture<TransportApiResponseMsg> getDeviceInfo(DeviceId deviceId) { |
|||
return Futures.transform(deviceService.findDeviceByIdAsync(deviceId), device -> { |
|||
if (device == null) { |
|||
log.trace("[{}] Failed to lookup device by id", deviceId); |
|||
return getEmptyTransportApiResponse(); |
|||
} |
|||
try { |
|||
return TransportApiResponseMsg.newBuilder() |
|||
.setValidateTokenResponseMsg(ValidateDeviceCredentialsResponseMsg.newBuilder().setDeviceInfo(getDeviceInfoProto(device)).build()).build(); |
|||
} catch (JsonProcessingException e) { |
|||
log.warn("[{}] Failed to lookup device by id", deviceId, e); |
|||
return getEmptyTransportApiResponse(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private DeviceInfoProto getDeviceInfoProto(Device device) throws JsonProcessingException { |
|||
return DeviceInfoProto.newBuilder() |
|||
.setTenantIdMSB(device.getTenantId().getId().getMostSignificantBits()) |
|||
.setTenantIdLSB(device.getTenantId().getId().getLeastSignificantBits()) |
|||
.setDeviceIdMSB(device.getId().getId().getMostSignificantBits()) |
|||
.setDeviceIdLSB(device.getId().getId().getLeastSignificantBits()) |
|||
.setDeviceName(device.getName()) |
|||
.setDeviceType(device.getType()) |
|||
.setAdditionalInfo(mapper.writeValueAsString(device.getAdditionalInfo())) |
|||
.build(); |
|||
} |
|||
|
|||
private ListenableFuture<TransportApiResponseMsg> getEmptyTransportApiResponseFuture() { |
|||
return Futures.immediateFuture(getEmptyTransportApiResponse()); |
|||
} |
|||
|
|||
private TransportApiResponseMsg getEmptyTransportApiResponse() { |
|||
return TransportApiResponseMsg.newBuilder() |
|||
.setValidateTokenResponseMsg(ValidateDeviceCredentialsResponseMsg.getDefaultInstance()).build(); |
|||
} |
|||
} |
|||
@ -0,0 +1,249 @@ |
|||
/** |
|||
* Copyright © 2016-2018 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.service.transport; |
|||
|
|||
import akka.actor.ActorRef; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.rule.engine.api.util.DonAsynchron; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.common.transport.SessionMsgListener; |
|||
import org.thingsboard.server.common.transport.TransportService; |
|||
import org.thingsboard.server.common.transport.TransportServiceCallback; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.DeviceActorToTransportMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToRPCMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; |
|||
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; |
|||
import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; |
|||
import org.thingsboard.server.service.encoding.DataDecodingEncodingService; |
|||
import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import javax.annotation.PreDestroy; |
|||
import java.util.Optional; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.SynchronousQueue; |
|||
import java.util.concurrent.ThreadPoolExecutor; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.function.Consumer; |
|||
|
|||
/** |
|||
* Created by ashvayka on 12.10.18. |
|||
*/ |
|||
@Slf4j |
|||
@Service |
|||
@ConditionalOnProperty(prefix = "transport", value = "type", havingValue = "local") |
|||
public class LocalTransportService implements TransportService, RuleEngineTransportService { |
|||
|
|||
private ConcurrentMap<UUID, SessionMsgListener> sessions = new ConcurrentHashMap<>(); |
|||
|
|||
private ExecutorService transportCallbackExecutor; |
|||
|
|||
@Autowired |
|||
private TransportApiService transportApiService; |
|||
|
|||
@Autowired |
|||
private ActorSystemContext actorContext; |
|||
|
|||
//TODO: completely replace this routing with the Kafka routing by partition ids.
|
|||
@Autowired |
|||
private ClusterRoutingService routingService; |
|||
@Autowired |
|||
private ClusterRpcService rpcService; |
|||
@Autowired |
|||
private DataDecodingEncodingService encodingService; |
|||
|
|||
@PostConstruct |
|||
public void init() { |
|||
this.transportCallbackExecutor = new ThreadPoolExecutor(0, 100, 60L, TimeUnit.SECONDS, new SynchronousQueue<>()); |
|||
} |
|||
|
|||
@PreDestroy |
|||
public void destroy() { |
|||
if (transportCallbackExecutor != null) { |
|||
transportCallbackExecutor.shutdownNow(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void process(ValidateDeviceTokenRequestMsg msg, TransportServiceCallback<ValidateDeviceCredentialsResponseMsg> callback) { |
|||
DonAsynchron.withCallback( |
|||
transportApiService.handle(TransportApiRequestMsg.newBuilder().setValidateTokenRequestMsg(msg).build()), |
|||
transportApiResponseMsg -> { |
|||
if (callback != null) { |
|||
callback.onSuccess(transportApiResponseMsg.getValidateTokenResponseMsg()); |
|||
} |
|||
}, |
|||
getThrowableConsumer(callback), transportCallbackExecutor); |
|||
} |
|||
|
|||
@Override |
|||
public void process(ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback<ValidateDeviceCredentialsResponseMsg> callback) { |
|||
DonAsynchron.withCallback( |
|||
transportApiService.handle(TransportApiRequestMsg.newBuilder().setValidateX509CertRequestMsg(msg).build()), |
|||
transportApiResponseMsg -> { |
|||
if (callback != null) { |
|||
callback.onSuccess(transportApiResponseMsg.getValidateTokenResponseMsg()); |
|||
} |
|||
}, |
|||
getThrowableConsumer(callback), transportCallbackExecutor); |
|||
} |
|||
|
|||
@Override |
|||
public void process(GetOrCreateDeviceFromGatewayRequestMsg msg, TransportServiceCallback<GetOrCreateDeviceFromGatewayResponseMsg> callback) { |
|||
DonAsynchron.withCallback( |
|||
transportApiService.handle(TransportApiRequestMsg.newBuilder().setGetOrCreateDeviceRequestMsg(msg).build()), |
|||
transportApiResponseMsg -> { |
|||
if (callback != null) { |
|||
callback.onSuccess(transportApiResponseMsg.getGetOrCreateDeviceResponseMsg()); |
|||
} |
|||
}, |
|||
getThrowableConsumer(callback), transportCallbackExecutor); |
|||
} |
|||
|
|||
@Override |
|||
public void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback<Void> callback) { |
|||
forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setSessionEvent(msg).build(), callback); |
|||
} |
|||
|
|||
@Override |
|||
public void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback<Void> callback) { |
|||
forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setPostTelemetry(msg).build(), callback); |
|||
} |
|||
|
|||
@Override |
|||
public void process(SessionInfoProto sessionInfo, PostAttributeMsg msg, TransportServiceCallback<Void> callback) { |
|||
forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setPostAttributes(msg).build(), callback); |
|||
} |
|||
|
|||
@Override |
|||
public void process(SessionInfoProto sessionInfo, GetAttributeRequestMsg msg, TransportServiceCallback<Void> callback) { |
|||
forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setGetAttributes(msg).build(), callback); |
|||
} |
|||
|
|||
@Override |
|||
public void process(SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback<Void> callback) { |
|||
forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setSubscribeToAttributes(msg).build(), callback); |
|||
} |
|||
|
|||
@Override |
|||
public void process(SessionInfoProto sessionInfo, SubscribeToRPCMsg msg, TransportServiceCallback<Void> callback) { |
|||
forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setSubscribeToRPC(msg).build(), callback); |
|||
} |
|||
|
|||
@Override |
|||
public void process(SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg msg, TransportServiceCallback<Void> callback) { |
|||
forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setToDeviceRPCCallResponse(msg).build(), callback); |
|||
} |
|||
|
|||
@Override |
|||
public void process(SessionInfoProto sessionInfo, ToServerRpcRequestMsg msg, TransportServiceCallback<Void> callback) { |
|||
forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setToServerRPCCallRequest(msg).build(), callback); |
|||
} |
|||
|
|||
@Override |
|||
public void registerSession(SessionInfoProto sessionInfo, SessionMsgListener listener) { |
|||
sessions.putIfAbsent(toId(sessionInfo), listener); |
|||
//TODO: monitor sessions periodically: PING REQ/RESP, etc.
|
|||
} |
|||
|
|||
@Override |
|||
public void deregisterSession(SessionInfoProto sessionInfo) { |
|||
sessions.remove(toId(sessionInfo)); |
|||
} |
|||
|
|||
private UUID toId(SessionInfoProto sessionInfo) { |
|||
return new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()); |
|||
} |
|||
|
|||
@Override |
|||
public void process(String nodeId, DeviceActorToTransportMsg msg) { |
|||
process(nodeId, msg, null, null); |
|||
} |
|||
|
|||
@Override |
|||
public void process(String nodeId, DeviceActorToTransportMsg msg, Runnable onSuccess, Consumer<Throwable> onFailure) { |
|||
UUID sessionId = new UUID(msg.getSessionIdMSB(), msg.getSessionIdLSB()); |
|||
SessionMsgListener listener = sessions.get(sessionId); |
|||
if (listener != null) { |
|||
transportCallbackExecutor.submit(() -> { |
|||
if (msg.hasGetAttributesResponse()) { |
|||
listener.onGetAttributesResponse(msg.getGetAttributesResponse()); |
|||
} |
|||
if (msg.hasAttributeUpdateNotification()) { |
|||
listener.onAttributeUpdate(msg.getAttributeUpdateNotification()); |
|||
} |
|||
if (msg.hasSessionCloseNotification()) { |
|||
listener.onRemoteSessionCloseCommand(msg.getSessionCloseNotification()); |
|||
} |
|||
if (msg.hasToDeviceRequest()) { |
|||
listener.onToDeviceRpcRequest(msg.getToDeviceRequest()); |
|||
} |
|||
if (msg.hasToServerResponse()) { |
|||
listener.onToServerRpcResponse(msg.getToServerResponse()); |
|||
} |
|||
}); |
|||
} else { |
|||
//TODO: should we notify the device actor about missed session?
|
|||
log.debug("[{}] Missing session.", sessionId); |
|||
} |
|||
if (onSuccess != null) { |
|||
onSuccess.run(); |
|||
} |
|||
} |
|||
|
|||
private void forwardToDeviceActor(TransportToDeviceActorMsg toDeviceActorMsg, TransportServiceCallback<Void> callback) { |
|||
TransportToDeviceActorMsgWrapper wrapper = new TransportToDeviceActorMsgWrapper(toDeviceActorMsg); |
|||
Optional<ServerAddress> address = routingService.resolveById(wrapper.getDeviceId()); |
|||
if (address.isPresent()) { |
|||
rpcService.tell(encodingService.convertToProtoDataMessage(address.get(), wrapper)); |
|||
} else { |
|||
actorContext.getAppActor().tell(wrapper, ActorRef.noSender()); |
|||
} |
|||
if (callback != null) { |
|||
callback.onSuccess(null); |
|||
} |
|||
} |
|||
|
|||
private <T> Consumer<Throwable> getThrowableConsumer(TransportServiceCallback<T> callback) { |
|||
return e -> { |
|||
if (callback != null) { |
|||
callback.onError(e); |
|||
} |
|||
}; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
<?xml version="1.0" encoding="UTF-8" ?> |
|||
<!-- |
|||
|
|||
Copyright © 2016-2018 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. |
|||
|
|||
--> |
|||
<!DOCTYPE configuration> |
|||
<configuration scan="true" scanPeriod="10 seconds"> |
|||
|
|||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> |
|||
<encoder> |
|||
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern> |
|||
</encoder> |
|||
</appender> |
|||
|
|||
<logger name="org.thingsboard.server" level="TRACE" /> |
|||
|
|||
<root level="INFO"> |
|||
<appender-ref ref="STDOUT"/> |
|||
</root> |
|||
|
|||
</configuration> |
|||
Loading…
Reference in new issue