Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Telemetry with two implementations: basic usage logging and opentelemetry.io observability #92

Merged
merged 17 commits into from
Jan 23, 2024
Merged
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
11 changes: 10 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,11 @@
<artifactId>hakunapi-cql2-functions</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>fi.nls.hakunapi</groupId>
<artifactId>hakunapi-telemetry</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
Expand Down Expand Up @@ -265,6 +269,11 @@
<artifactId>log4j-core</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-web</artifactId>
<version>${log4j.version}</version>
</dependency>

<dependency>
<groupId>junit</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import fi.nls.hakunapi.core.schemas.ConformanceClasses;
import fi.nls.hakunapi.core.schemas.FunctionsContent;
import fi.nls.hakunapi.core.telemetry.ServiceTelemetry;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
Expand All @@ -36,6 +37,7 @@ public abstract class FeatureServiceConfig {
protected FunctionsContent functionsContent;
protected List<MetadataFormat> metadataFormats;
protected List<SRIDCode> knownSrids;
protected ServiceTelemetry telemetry = ServiceTelemetry.NOP;

public int getLimitDefault() {
return limitDefault;
Expand Down Expand Up @@ -216,4 +218,13 @@ public String getApiKeyQueryParam() {
.filter(it -> it.getIn() == In.QUERY).findAny().map(it -> it.getName()).orElse(null);
}

public ServiceTelemetry getTelemetry() {
return telemetry;
}

public void setTelemetry(ServiceTelemetry usage) {
this.telemetry = usage;
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package fi.nls.hakunapi.core.telemetry;

public interface RequestTelemetry {

public TelemetrySpan span();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package fi.nls.hakunapi.core.telemetry;

import java.io.Closeable;
import java.io.IOException;
import java.util.Map;

import fi.nls.hakunapi.core.FeatureServiceConfig;
import fi.nls.hakunapi.core.config.HakunaConfigParser;
import fi.nls.hakunapi.core.request.GetFeatureRequest;

public interface ServiceTelemetry extends Closeable {

public String getId();

public void setName(String name);
public String getName();
public void setHeaders(Map<String, String> headersMap) ;
public void setCollections(Map<String, String> collectionsMap);

public void parse(FeatureServiceConfig service, HakunaConfigParser parser);
public void start();

public RequestTelemetry forRequest(GetFeatureRequest r);


// No op implementation
static ServiceTelemetry NOP = new ServiceTelemetry() {

String name;
@Override
public RequestTelemetry forRequest(GetFeatureRequest r) {
return NOPFeatureTypeTelemetry.NOP;

}

@Override
public void setName(String name) {
this.name = name;
}

@Override
public String getName() {
return name;
}

@Override
public void setHeaders(Map<String, String> headersMap) {
}

@Override
public String getId() {
return "nop";
}

@Override
public void setCollections(Map<String, String> collectionsMap) {
}

@Override
public void parse(FeatureServiceConfig service, HakunaConfigParser parser) {
}

@Override
public void start() {
}

@Override
public void close() throws IOException {
}

};

public static class NOPFeatureTypeTelemetry implements RequestTelemetry {


public static final NOPFeatureTypeTelemetry NOP = new NOPFeatureTypeTelemetry();

@Override
public TelemetrySpan span() {
return TelemetrySpan.NOP;
}
}




}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package fi.nls.hakunapi.core.telemetry;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import fi.nls.hakunapi.core.FeatureServiceConfig;
import fi.nls.hakunapi.core.FeatureType;
import fi.nls.hakunapi.core.config.HakunaConfigParser;

public class TelemetryConfigParser {

private static final Logger LOG = LoggerFactory.getLogger(TelemetryConfigParser.class);

protected static final String TELEMETRY_LOG_NOP = "NOP";
public static final String TELEMETRY_MODULE_PROP = "telemetry.module.%s";
protected static final String TELEMETRY_MODE = "telemetry.mode";
protected static final String TELEMETRY_LOGGER = "telemetry.logger";
protected static final String TELEMETRY_FIELDS = "telemetry.fields";
protected static final String TELEMETRY_FIELDS_HEADER = "telemetry.fields.%s.header";
protected static final String TELEMETRY_COLLECTIONS = "telemetry.collections";
protected static final String TELEMETRY_COLLECTIONS_NAME = "telemetry.collections.%s.name";
protected static final String TELEMETRY_COLLECTIONS_WILDCARD = "*";


public static ServiceTelemetry parse(FeatureServiceConfig service, HakunaConfigParser parser) {

String telemetryId = parser.get(TELEMETRY_MODE, TELEMETRY_LOG_NOP);
if (TELEMETRY_LOG_NOP.equals(telemetryId)) {
LOG.info("Using no-op telemetry");
return ServiceTelemetry.NOP;
}

ServiceTelemetry telemetry = TelemetryProvider.getTelemetries().get(telemetryId);
if(telemetry==null) {
LOG.info("Telemetry not found "+telemetryId+". Using no-op telemetry");
return ServiceTelemetry.NOP;
}

LOG.info("Using telemetry "+telemetry.getClass().getName());

String usageLogName = parser.get(TELEMETRY_LOGGER);
telemetry.setName(usageLogName);

String[] headers = parser.getMultiple(TELEMETRY_FIELDS);

Map<String, String> headersMap = new HashMap<>();
telemetry.setHeaders(headersMap);
if (headers != null) {
for (String header : headers) {
String lookup = parser.get(String.format(TELEMETRY_FIELDS_HEADER, header), header);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Took a while to understand how this works, but it should.

headersMap.put(header, lookup);
}
}

String collectionsWildcard = parser.get(TELEMETRY_COLLECTIONS);
if (collectionsWildcard == null) {
return ServiceTelemetry.NOP;

} else if (service != null && TELEMETRY_COLLECTIONS_WILDCARD.equals(collectionsWildcard)) {
Collection<FeatureType> collections = service.getCollections();

// defaults to logging ft.name as ft.name
Map<String, String> collectionsMap = collections.stream()
.collect(Collectors.toMap(FeatureType::getName, FeatureType::getName));
telemetry.setCollections(collectionsMap);

} else {
String[] collections = parser.getMultiple(TELEMETRY_COLLECTIONS);
Map<String, String> collectionsMap = new HashMap<>();
telemetry.setCollections(collectionsMap);

// defaults to logging ft.name as ft.name with optional rename
if (collections != null) {
for (String collection : collections) {
String lookup = parser.get(String.format(TELEMETRY_COLLECTIONS_NAME, collection), collection);
collectionsMap.put(collection, lookup);
}
}
}

telemetry.parse(service,parser);

return telemetry;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package fi.nls.hakunapi.core.telemetry;

public interface TelemetryFactory {

public ServiceTelemetry createServiceTelemetry();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package fi.nls.hakunapi.core.telemetry;

import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;


public class TelemetryProvider {

private static final ServiceLoader<TelemetryFactory> LOADER =
ServiceLoader.load(TelemetryFactory.class);

public static Map<String, ServiceTelemetry> getTelemetries() {
LOADER.reload();

final Map<String, ServiceTelemetry> telemetries = new HashMap<>();

for (TelemetryFactory factory : LOADER) {
ServiceTelemetry telemetry = factory.createServiceTelemetry();
telemetries.put(telemetry.getId(), telemetry);
}
return telemetries;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package fi.nls.hakunapi.core.telemetry;

import fi.nls.hakunapi.core.request.WriteReport;

public interface TelemetrySpan extends AutoCloseable {

void counts(int count);
void counts(WriteReport report);
void put(String key, String value);

public void close();

// No op implementation
TelemetrySpan NOP = new TelemetrySpan() {

@Override
public void close() {
}

@Override
public void counts(int count) {
}

@Override
public void counts(WriteReport report) {
}

@Override
public void put(String key, String value) {
}

};

}
Loading
Loading