Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright The Microcks 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 io.github.microcks.domain;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import java.util.Date;

@Document("invocations")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this one is necessary. I prefer to limit the number of dependencies to spring-data stuff to the minimum.

public class InvocationLogEntry {
@Id
private Long timestampEpoch;
private String serviceName;
/**
*
*/
Comment on lines +28 to +30
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove unnecessary comments.

private String serviceVersion;
/**
*
*/
private String mockResponse;
/**
*
*/
private Date invocationTimestamp;
/**
*
*/
private long duration;
private String source;
private String requestId;

public InvocationLogEntry(Long timestampEpoch, String serviceName, String serviceVersion, String mockResponse,
Date invocationTimestamp, long duration, String source, String requestId) {
this.timestampEpoch = timestampEpoch;
this.serviceName = serviceName;
this.serviceVersion = serviceVersion;
this.mockResponse = mockResponse;
this.invocationTimestamp = invocationTimestamp;
this.duration = duration;
this.source = source;
this.requestId = requestId;
}

public InvocationLogEntry() {
}

public Long getTimestampEpoch() {
return timestampEpoch;
}

public void setTimestampEpoch(Long timestampEpoch) {
this.timestampEpoch = timestampEpoch;
}

public String getServiceName() {
return serviceName;
}

public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}

public String getServiceVersion() {
return serviceVersion;
}

public void setServiceVersion(String serviceVersion) {
this.serviceVersion = serviceVersion;
}

public String getMockResponse() {
return mockResponse;
}

public void setMockResponse(String mockResponse) {
this.mockResponse = mockResponse;
}

public Date getInvocationTimestamp() {
return invocationTimestamp;
}

public void setInvocationTimestamp(Date invocationTimestamp) {
this.invocationTimestamp = invocationTimestamp;
}

public long getDuration() {
return duration;
}

public void setDuration(long duration) {
this.duration = duration;
}

public String getSource() {
return source;
}

public void setSource(String source) {
this.source = source;
}

public String getRequestId() {
return requestId;
}

public void setRequestId(String requestId) {
this.requestId = requestId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public class Operation {

private Set<String> resourcePaths;
private List<ParameterConstraint> parameterConstraints;
private String idPath;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is actually the idPath?


public String getName() {
return name;
Expand Down Expand Up @@ -162,4 +163,12 @@ public void addParameterConstraint(ParameterConstraint constraint) {
}
parameterConstraints.add(constraint);
}

public void setIdPath(String idPath) {
this.idPath = idPath;
}

public String getIdPath() {
return idPath;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public class MockInvocationEvent extends ApplicationEvent {
private final Date invocationTimestamp;
/** */
private final long duration;
/** */
private final String requestId;

/**
* Create a new mock invocation event.
Expand All @@ -46,13 +48,14 @@ public class MockInvocationEvent extends ApplicationEvent {
* @param duration Duration of invocation
*/
public MockInvocationEvent(Object source, String serviceName, String serviceVersion, String mockResponse,
Date invocationTimestamp, long duration) {
Date invocationTimestamp, long duration, String requestId) {
super(source);
this.serviceName = serviceName;
this.serviceVersion = serviceVersion;
this.mockResponse = mockResponse;
this.invocationTimestamp = invocationTimestamp;
this.duration = duration;
this.requestId = requestId;
}

public String getServiceName() {
Expand All @@ -74,4 +77,8 @@ public Date getInvocationTimestamp() {
public long getDuration() {
return duration;
}

public String getRequestId() {
return requestId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright The Microcks 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 io.github.microcks.listener;

import io.github.microcks.domain.InvocationLogEntry;
import io.github.microcks.event.MockInvocationEvent;
import io.github.microcks.repository.InvocationLogRepository;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationListener;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;

/**
* Log invocations to database, listens to MockInvocationEvent, properties mocks.enable-invocation-logs and
* mocks.enable-invocation-stats need to be enabled to use this.
*/
@Component
@ConditionalOnProperty(name = "mocks.enable-invocation-logs", havingValue = "true")
public class InvocationAdvancedMetrics implements ApplicationListener<MockInvocationEvent> {

final InvocationLogRepository repo;

public InvocationAdvancedMetrics(InvocationLogRepository repo) {
this.repo = repo;
}

@Override
public void onApplicationEvent(@NonNull MockInvocationEvent event) {
repo.insert(new InvocationLogEntry(event.getTimestamp(), event.getServiceName(), event.getServiceVersion(),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace event.getTimestamp() with event.getInvocationTimestamp().getTime() to ensure a valid epoch value is passed to the log entry.

repo.insert(new InvocationLogEntry(
    event.getInvocationTimestamp().getTime(),
    event.getServiceName(),
    event.getServiceVersion(),
    event.getMockResponse(),
    event.getInvocationTimestamp(),
    event.getDuration(),
    event.getSource().getClass().getSimpleName(),
    event.getRequestId()
));

event.getMockResponse(), event.getInvocationTimestamp(), event.getDuration(),
event.getSource().getClass().getSimpleName(), event.getRequestId()));
}

@Override
public boolean supportsAsyncExecution() {
return ApplicationListener.super.supportsAsyncExecution();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright The Microcks 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 io.github.microcks.repository;

import io.github.microcks.domain.InvocationLogEntry;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.repository.NoRepositoryBean;
import java.util.List;

/**
* Repository for InvocationLogEntries
*/
@NoRepositoryBean
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This construct and way to define repository is different than the one we used for DailyStatisticRepository. I find it a bit more difficult to understand - especially the InvocationLogRepositoryImpl part with the MongoEntityInformation override.

I think it could be nice to refactor reusing the same pattern here. What do you think?

public interface InvocationLogRepository extends MongoRepository<InvocationLogEntry, Long> {

/**
* find the latest invocation log entries from database
* @param service Service to query
* @param version Version of the service to query
* @param limit maximum number of entries
* @return List of log entries ordered by newest entry first
*/
List<InvocationLogEntry> findLastEntriesByServiceName(String service, String version, int limit);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright The Microcks 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 io.github.microcks.repository;

import io.github.microcks.domain.InvocationLogEntry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.mongodb.repository.support.SimpleMongoRepository;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
* Implementation for InvocationLogRepository
*/
@Repository
public class InvocationLogRepositoryImpl extends SimpleMongoRepository<InvocationLogEntry, Long>
implements InvocationLogRepository {

@Autowired
private MongoTemplate mongoTemplate;

public InvocationLogRepositoryImpl(MongoOperations mongoOperations) {
super(new MongoEntityInformation<>() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment on interface definition.

@Override
@NonNull
public Class<InvocationLogEntry> getJavaType() {
return InvocationLogEntry.class;
}

@Override
public boolean isNew(@NonNull InvocationLogEntry entity) {
return false;
}

@Override
public Long getId(@NonNull InvocationLogEntry entity) {
return entity.getInvocationTimestamp().toInstant().toEpochMilli();
}

@Override
@NonNull
public Class<Long> getIdType() {
return Long.class;
}

@Override
@NonNull
public String getCollectionName() {
return "invocations";
}

@Override
@NonNull
public String getIdAttribute() {
return "invocationTimestamp";
}

@Override
public Collation getCollation() {
return null;
}
}, mongoOperations);
}

/**
* find the latest invocation log entries from database
* @param service Service to query
* @param version Version of the service to query
* @param limit maximum number of entries
* @return List of log entries ordered by newest entry first
*/
@Override
public List<InvocationLogEntry> findLastEntriesByServiceName(String service, String version, int limit) {
Query query = new Query().addCriteria(Criteria.where("serviceName").is(service))
.addCriteria(Criteria.where("serviceVersion").is(version))
.with(Sort.by(Sort.Direction.DESC, "invocationTimestamp")).limit(limit);
return mongoTemplate.find(query, InvocationLogEntry.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,8 @@ public static void completeOperationProperties(Operation operation, JsonNode nod
if (node.has("dispatcherRules")) {
operation.setDispatcherRules(node.path("dispatcherRules").asText());
}
if (node.has("requestIdPath")) {
operation.setIdPath(node.path("requestIdPath").asText());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ private void waitForDelay(Long since, Long delay, MockContext mockContext) {
if (enableInvocationStats) {
MockInvocationEvent event = new MockInvocationEvent(this, mockContext.service.getName(),
mockContext.service.getVersion(), "DynamicMockRestController", new Date(since),
since - System.currentTimeMillis());
since - System.currentTimeMillis(), "");
applicationContext.publishEvent(event);
log.debug("Mock invocation event has been published");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ public ResponseEntity<?> execute(@PathVariable("service") String serviceName,
// Publish an invocation event before returning if enabled.
if (Boolean.TRUE.equals(enableInvocationStats)) {
MockControllerCommons.publishMockInvocation(applicationContext, this, service,
graphqlResponses.get(0).getResponse(), startTime);
graphqlResponses.get(0).getResponse(), startTime, "");
}

String responseContent = null;
Expand Down
Loading