Skip to content

Instantly share code, notes, and snippets.

@ibaca
Last active July 24, 2020 06:00
Show Gist options
  • Save ibaca/bcd1fccf931d6eca8ba827c5bd37a1db to your computer and use it in GitHub Desktop.
Save ibaca/bcd1fccf931d6eca8ba827c5bd37a1db to your computer and use it in GitHub Desktop.
JAX-RS GWT Logging service
package com.intendia.igestion.server.util;
import static com.intendia.igestion.shared.util.ToolsRestService.logRecordFromDto;
import com.google.gwt.core.server.StackTraceDeobfuscator;
import com.intendia.igestion.shared.util.ToolsRestService;
import io.reactivex.Completable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import javax.servlet.ServletContext;
@Singleton
public class DefaultLoggingRestService implements LoggingRestService {
private final StackTraceDeobfuscator deobfuscator = new ClasspathStackTraceDeobfuscator();
@Inject Provider<ServletContext> contextProvider;
@Override
public Completable sendLog(String permutation, LogRecordDto log) {
logMessage(permutation, logRecordFromDto(log));
return Completable.complete();
}
public void logMessage(String permutation, LogRecord record) {
if (record.getThrown() != null) deobfuscator.deobfuscateStackTrace(record.getThrown(), permutation);
Logger.getLogger(record.getLoggerName()).log(record);
}
private final class ClasspathStackTraceDeobfuscator extends StackTraceDeobfuscator {
public static final String SYMBOL_MAPS_DIRECTORY = "/WEB-INF/deploy/web/symbolMaps/";
@Override
protected InputStream getSourceMapInputStream(String permutation, int fragmentNumber) throws IOException {
return getInputStream(SYMBOL_MAPS_DIRECTORY + permutation + "_sourceMap" + fragmentNumber + ".json");
}
@Override
protected InputStream getSymbolMapInputStream(String permutation) throws IOException {
return getInputStream(SYMBOL_MAPS_DIRECTORY + permutation + ".symbolMap");
}
@Override
protected InputStream openInputStream(String fileName) throws IOException {
return new FileInputStream(new File(SYMBOL_MAPS_DIRECTORY, fileName));
}
private InputStream getInputStream(String path) throws FileNotFoundException {
final InputStream stream = contextProvider.get().getResourceAsStream(path);
// don't return null which trows a NPE and escapes to the parent. FNFE it's correctly caught
if (stream == null) throw new FileNotFoundException(path);
return stream;
}
}
}
package com.intendia.igestion.shared.util;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.intendia.igestion.shared.domain.ApplicationConstants.APPLICATION_JSON;
import static com.intendia.igestion.shared.domain.ApplicationConstants.RESOURCE_TOOLS;
import static jsinterop.annotations.JsPackage.GLOBAL;
import com.intendia.gwt.autorest.client.AutoRestGwt;
import io.reactivex.Completable;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import jsinterop.annotations.JsType;
/** Common application utilities like version details, build info or system info. */
@AutoRestGwt
@Path(RESOURCE_TOOLS) @Produces(APPLICATION_JSON) @Consumes(APPLICATION_JSON)
public interface LoggingRestService {
@POST @Path("log/{permutation}")
Completable sendLog(@PathParam("permutation") String permutation, LogRecordDto log);
@JsType(namespace = GLOBAL, name = "Object", isNative = true)//
class LogRecordDto {
public String level;
public String loggerName;
public String msg;
public double timestamp;
public @Nullable ThrowableDto thrown;
}
@JsType(namespace = GLOBAL, name = "Object", isNative = true)//
class ThrowableDto {
public String type;
public String message;
public @Nullable ThrowableDto cause;
public StackTraceElementDto[] stackTrace;
}
@JsType(namespace = GLOBAL, name = "Object", isNative = true)//
class StackTraceElementDto {
public String className;
public String fileName;
public String methodName;
public int lineNumber;
}
static LogRecordDto logRecordToDto(LogRecord record) {
LogRecordDto out = new LogRecordDto();
out.level = record.getLevel().toString();
out.loggerName = record.getLoggerName();
out.msg = record.getMessage();
out.timestamp = record.getMillis();
out.thrown = throwableToDto(record.getThrown());
return out;
}
static @Nullable ThrowableDto throwableToDto(@Nullable Throwable throwable) {
if (throwable == null) return null;
ThrowableDto out = new ThrowableDto();
out.type = throwable.getClass().getName();
out.message = throwable.getMessage();
out.cause = throwableToDto(throwable.getCause());
out.stackTrace = Stream.of(firstNonNull(throwable.getStackTrace(), new StackTraceElement[0]))
.map(eIn -> {
StackTraceElementDto eOut = new StackTraceElementDto();
eOut.className = eIn.getClassName();
eOut.fileName = eIn.getFileName();
eOut.methodName = eIn.getMethodName();
eOut.lineNumber = eIn.getLineNumber();
return eOut;
}).toArray(StackTraceElementDto[]::new);
return out;
}
static LogRecord logRecordFromDto(LogRecordDto dto) {
LogRecord lr = new LogRecord(Level.parse(dto.level), dto.msg);
lr.setLoggerName(dto.loggerName);
lr.setThrown(JsonLogRecordThrowable.fromJsonString(dto.thrown));
lr.setMillis((long) dto.timestamp);
return lr;
}
@SuppressWarnings("GwtInconsistentSerializableClass")//
class JsonLogRecordThrowable extends Throwable {
private final String type;
private static @Nullable Throwable fromJsonString(@Nullable ThrowableDto dto) {
return dto == null ? null : new JsonLogRecordThrowable(dto);
}
JsonLogRecordThrowable(ThrowableDto dto) {
super(dto.message, fromJsonString(dto.cause));
this.type = dto.type;
this.setStackTrace(Stream.of(firstNonNull(dto.stackTrace, new StackTraceElementDto[0]))
.map(e -> new StackTraceElement(e.className, e.methodName, e.fileName, e.lineNumber))
.toArray(StackTraceElement[]::new));
}
public String toString() { return getMessage() != null ? type + ": " + getMessage() : type; }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment