Skip to content

Commit

Permalink
V0.3.0 - Add possibility to report unexpected exceptions to bugsnag (#6)
Browse files Browse the repository at this point in the history
* Add feature to provide a bugsnag api key to report unexpected exceptions to bugsnag
  • Loading branch information
BolZer authored Oct 31, 2024
1 parent 7600164 commit 5a566c3
Show file tree
Hide file tree
Showing 14 changed files with 168 additions and 30 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,22 @@ final class EN16931Validator
}
```

## Insights
You may enable bug reporting via Bugsnag by supplying the env-variable `BUGSNAG_API_KEY`.
```yaml
en16931-validator:
image: 'easybill/en16931-validator:latest'
ports:
- '8081:8080'
environment:
JAVA_TOOL_OPTIONS: -Xmx512m
BUGSNAG_API_KEY: <YOUR_API_KEY>
healthcheck:
test: curl --fail http://localhost:8081/health || exit 0
interval: 10s
retries: 6
```
## Issues & Contribution
Feel free to create pull-requests or issues if you have trouble with this service or any related resources.
Expand Down
4 changes: 3 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
plugins {
java
id("io.quarkus")
id("io.quarkus") version "3.16.1"
id("com.diffplug.spotless") version "6.25.0"
id("com.github.spotbugs") version "6.0.15"
id("org.checkerframework") version "0.6.44"
Expand Down Expand Up @@ -28,6 +28,8 @@ dependencies {
implementation("com.helger.commons:ph-commons:11.1.6")
implementation("com.helger.schematron:ph-schematron-api:7.1.0")
implementation("com.helger.schematron:ph-schematron-xslt:8.0.0")
implementation("io.quarkus:quarkus-arc")
implementation("com.bugsnag:bugsnag:3.+")

testImplementation("io.quarkus:quarkus-junit5")
testImplementation("io.rest-assured:rest-assured")
Expand Down
27 changes: 27 additions & 0 deletions src/main/java/io/github/easybill/Contracts/IApplicationConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.github.easybill.Contracts;

import static io.smallrye.config.ConfigMapping.NamingStrategy.VERBATIM;

import io.quarkus.runtime.annotations.StaticInitSafe;
import io.smallrye.config.ConfigMapping;
import io.smallrye.config.WithName;
import java.util.Optional;

@StaticInitSafe
@ConfigMapping(prefix = "app", namingStrategy = VERBATIM)
public interface IApplicationConfig {
String version();

Artefacts artefacts();

interface Artefacts {
String version();
}

Exceptions exceptions();

interface Exceptions {
@WithName("bugsnag-api-key")
Optional<String> bugsnagApiKey();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.github.easybill.Contracts;

public interface IExceptionNotifier {
void notify(Throwable throwable);
}
7 changes: 7 additions & 0 deletions src/main/java/io/github/easybill/Contracts/IStageService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.github.easybill.Contracts;

public interface IStageService {
boolean isProd();
boolean isDev();
boolean isTest();
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.easybill.Interceptors;

import io.github.easybill.Contracts.IExceptionNotifier;
import io.github.easybill.Exceptions.InvalidXmlException;
import io.github.easybill.Exceptions.ParsingException;
import jakarta.ws.rs.WebApplicationException;
Expand All @@ -9,12 +10,19 @@
import org.jboss.logging.Logger;

@Provider
public class GlobalExceptionInterceptor implements ExceptionMapper<Throwable> {
public final class GlobalExceptionInterceptor
implements ExceptionMapper<Throwable> {

private static final Logger LOGGER = Logger.getLogger(
private final IExceptionNotifier exceptionNotifier;

private static final Logger logger = Logger.getLogger(
GlobalExceptionInterceptor.class
);

public GlobalExceptionInterceptor(IExceptionNotifier exceptionNotifier) {
this.exceptionNotifier = exceptionNotifier;
}

@Override
public Response toResponse(Throwable exception) {
if (exception instanceof WebApplicationException) {
Expand All @@ -31,7 +39,9 @@ public Response toResponse(Throwable exception) {
.build();
}

LOGGER.error("Encountered an exception:", exception);
exceptionNotifier.notify(exception);

logger.error("Encountered an exception:", exception);

return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import org.jboss.logging.Logger;

@Provider
public class RequestResponseLoggingInterceptor
public final class RequestResponseLoggingInterceptor
implements ContainerRequestFilter, ContainerResponseFilter {

private static final Logger logger = Logger.getLogger(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.github.easybill.Services.BugNotifier;

import com.bugsnag.Bugsnag;
import io.github.easybill.Contracts.IExceptionNotifier;
import org.checkerframework.checker.nullness.qual.NonNull;

public final class BugsnagNotifier implements IExceptionNotifier {

@NonNull
private final String bugsnagApiKey;

public BugsnagNotifier(@NonNull String bugsnagApiKey) {
this.bugsnagApiKey = bugsnagApiKey;
}

public void notify(Throwable throwable) {
try (Bugsnag bugsnag = new Bugsnag(bugsnagApiKey)) {
bugsnag.notify(throwable);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.github.easybill.Services.BugNotifier;

import io.github.easybill.Contracts.IExceptionNotifier;

public final class NoopNotifier implements IExceptionNotifier {

@Override
public void notify(Throwable throwable) {
return;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package io.github.easybill.Services.BugNotifier;

import io.github.easybill.Contracts.IApplicationConfig;
import io.github.easybill.Contracts.IExceptionNotifier;
import io.github.easybill.Interceptors.GlobalExceptionInterceptor;
import io.quarkus.arc.DefaultBean;
import io.quarkus.arc.profile.IfBuildProfile;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.Dependent;
import jakarta.ws.rs.Produces;
import org.jboss.logging.Logger;

@Dependent
public final class NotifierProducer {

private static final Logger logger = Logger.getLogger(
GlobalExceptionInterceptor.class
);

@Produces
@ApplicationScoped
@IfBuildProfile("prod")
public IExceptionNotifier realNotifier(IApplicationConfig config) {
var bugsnagApiKey = config.exceptions().bugsnagApiKey();

if (bugsnagApiKey.isEmpty()) {
logger.info(
"Notifier: NOOP notifier loaded as no API-Key was provided"
);

return new NoopNotifier();
}

logger.info("Notifier: BUGSNAG notifier loaded");

return new BugsnagNotifier(bugsnagApiKey.get());
}

@Produces
@DefaultBean
@ApplicationScoped
public IExceptionNotifier noopNotifier() {
logger.info(
"Notifier: NOOP notifier loaded as the build profile does not permit loading a real notifier"
);

return new NoopNotifier();
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package io.github.easybill.Services.HealthCheck;

import io.github.easybill.Contracts.IApplicationConfig;
import jakarta.enterprise.context.ApplicationScoped;
import java.lang.management.ManagementFactory;
import java.util.Objects;
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.HealthCheckResponseBuilder;
Expand All @@ -13,9 +13,9 @@
@ApplicationScoped
public final class ApplicationHealthCheck implements HealthCheck {

final Config config;
final IApplicationConfig config;

public ApplicationHealthCheck(Config config) {
public ApplicationHealthCheck(IApplicationConfig config) {
this.config = config;
}

Expand All @@ -30,12 +30,7 @@ public HealthCheckResponse call() {

return response
.up()
.withData(
"version",
Objects.requireNonNull(
config.getConfigValue("application.version").getValue()
)
)
.withData("version", Objects.requireNonNull(config.version()))
.withData("osName", osBean.getName())
.withData("osArch", osBean.getArch())
.withData(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package io.github.easybill.Services.HealthCheck;

import io.github.easybill.Contracts.IApplicationConfig;
import io.github.easybill.Contracts.IValidationService;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.Objects;
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.HealthCheckResponseBuilder;
Expand All @@ -13,12 +13,12 @@
@ApplicationScoped
public final class ValidatorHealthCheck implements HealthCheck {

final Config config;
final IApplicationConfig config;
final IValidationService validationService;

public ValidatorHealthCheck(
IValidationService validationService,
Config config
IApplicationConfig config
) {
this.config = config;
this.validationService = validationService;
Expand All @@ -32,9 +32,7 @@ public HealthCheckResponse call() {

response.withData(
"artefactsVersion",
Objects.requireNonNull(
config.getConfigValue("en16931.artefacts.version").getValue()
)
Objects.requireNonNull(config.artefacts().version())
);

if (this.validationService.isLoadedSchematronValid()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.helger.schematron.sch.SchematronResourceSCH;
import com.helger.schematron.svrl.jaxb.FailedAssert;
import com.helger.schematron.svrl.jaxb.SchematronOutputType;
import io.github.easybill.Contracts.IApplicationConfig;
import io.github.easybill.Contracts.IValidationService;
import io.github.easybill.Dtos.ValidationResult;
import io.github.easybill.Dtos.ValidationResultField;
Expand All @@ -20,7 +21,6 @@
import java.util.Optional;
import java.util.regex.Pattern;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.eclipse.microprofile.config.Config;
import org.mozilla.universalchardet.UniversalDetector;

@Singleton
Expand All @@ -30,11 +30,8 @@ public final class ValidationService implements IValidationService {
private final SchematronResourceSCH ciiSchematron;
private final SchematronResourceSCH ublSchematron;

ValidationService(Config config) {
artefactsVersion =
Objects.requireNonNull(
config.getConfigValue("en16931.artefacts.version").getValue()
);
ValidationService(IApplicationConfig config) {
artefactsVersion = config.artefacts().version();

ciiSchematron =
new SchematronResourceSCH(
Expand Down
8 changes: 4 additions & 4 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
application.version=0.2.0

en16931.artefacts.version=1.3.13
app.version=0.3.0
app.artefacts.version=1.3.13
app.exceptions.bugsnag-api-key=${BUGSNAG_API_KEY}

quarkus.smallrye-openapi.info-title=EN16931 Validator API
quarkus.smallrye-openapi.info-version=${application.version}
quarkus.smallrye-openapi.info-version=${app.version}
quarkus.smallrye-openapi.info-description=A small service to validate XML against EN16931 rules
quarkus.smallrye-openapi.info-contact-email[email protected]
quarkus.smallrye-openapi.info-contact-name=easybill GmbH
Expand Down

0 comments on commit 5a566c3

Please sign in to comment.