Access to logs from all applications

Hi guys,

Problem:

In modern software development, logs doesn’t live in files – they live in databases.

We would like to be able to ship the logs from all XP-applications to a log-aggregation-service (Sentry for us). This will give us insight into all errors happening on the XP-server, enabling us to take action and fix those issues quickly.

Current situation:

We are able catch errors in our own applications, and ship the error events, but we can’t do this to 3rd-party applications, and it doesn’t feel right to do it in our open source applications on Enonic Market.

So we will always have a huge blind spot in the logs, unless we are able to forward all the logs.


What I have tried:

Getting the Root-logger

dependencies {
  include "ch.qos.logback:logback-classic:1.5.18"
  include "org.slf4j:slf4j-simple:2.0.17"
  include "org.slf4j:slf4j-api:2.0.17"
}

Getting access to the root-logger to register a new “appender”.

// import ch.qos.logback.classic.Logger;
// import ch.qos.logback.classic.LoggerContext;
LoggerContext loggerContext = new LoggerContext();
Logger rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);

This results in:

java.util.concurrent.CompletionException: java.lang.NoClassDefFoundError: org/slf4j/spi/LoggingEventAware
	at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:315)
	at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:320)
	at java.base/java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:649)
	at java.base/java.util.concurrent.CompletableFuture$Completion.run(CompletableFuture.java:482)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.NoClassDefFoundError: org/slf4j/spi/LoggingEventAware
	at java.base/java.lang.ClassLoader.defineClass1(Native Method)
	at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1017)
	at org.apache.felix.framework.BundleWiringImpl$BundleClassLoader.defineClass(BundleWiringImpl.java:2338)
	at org.apache.felix.framework.BundleWiringImpl$BundleClassLoader.defineClassParallel(BundleWiringImpl.java:2156)
	at org.apache.felix.framework.BundleWiringImpl$BundleClassLoader.findClass(BundleWiringImpl.java:2090)
	at org.apache.felix.framework.BundleWiringImpl.findClassOrResourceByDelegation(BundleWiringImpl.java:1556)
	at org.apache.felix.framework.BundleWiringImpl.access$300(BundleWiringImpl.java:79)
	at org.apache.felix.framework.BundleWiringImpl$BundleClassLoader.loadClass(BundleWiringImpl.java:1976)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
	at ch.qos.logback.classic.LoggerContext.<init>(LoggerContext.java:90)
	at no.item.sentry.SentryHandler.start(SentryHandler.java:24)
	at org.openjdk.nashorn.internal.scripts.Script$Recompilation$313$177$main.L:1#start(no.item.sentry:/main.js:5)
	at org.openjdk.nashorn.internal.scripts.Script$Recompilation$312$440$main.L:1#L:13(no.item.sentry:/main.js:14)
	at org.openjdk.nashorn.internal.runtime.ScriptFunctionData.invoke(ScriptFunctionData.java:646)
	at org.openjdk.nashorn.internal.runtime.ScriptFunction.invoke(ScriptFunction.java:513)
	at org.openjdk.nashorn.internal.runtime.ScriptRuntime.apply(ScriptRuntime.java:520)
	at org.openjdk.nashorn.javaadapters.java_util_concurrent_Callable.call(Unknown Source)
	at com.enonic.xp.context.ContextImpl.callWith(ContextImpl.java:100)
	at com.enonic.xp.lib.context.ContextHandlerBean.run(ContextHandlerBean.java:36)
	at org.openjdk.nashorn.internal.scripts.Script$Recompilation$311$1299AA$context.L:1#run(no.item.sentry:/lib/xp/context.js:51)
	at org.openjdk.nashorn.internal.scripts.Script$Recompilation$308$1AAAAAA$main.L:1(no.item.sentry:/main.js:10)
	at org.openjdk.nashorn.internal.runtime.ScriptFunctionData.invoke(ScriptFunctionData.java:678)
	at org.openjdk.nashorn.internal.runtime.ScriptFunction.invoke(ScriptFunction.java:513)
	at org.openjdk.nashorn.internal.runtime.ScriptRuntime.apply(ScriptRuntime.java:520)
	at org.openjdk.nashorn.api.scripting.ScriptObjectMirror.call(ScriptObjectMirror.java:111)
	at com.enonic.xp.script.impl.executor.ScriptExecutorImpl.executeRequire(ScriptExecutorImpl.java:164)
	at com.enonic.xp.script.impl.executor.ScriptExecutorImpl.requireJs(ScriptExecutorImpl.java:216)
	at com.enonic.xp.script.impl.executor.ScriptExecutorImpl.requireJsOrJson(ScriptExecutorImpl.java:205)
	at com.enonic.xp.script.impl.executor.ScriptExportsCache.getOrCompute(ScriptExportsCache.java:53)
	at com.enonic.xp.script.impl.executor.ScriptExecutorImpl.executeRequire(ScriptExecutorImpl.java:135)
	at com.enonic.xp.script.impl.executor.ScriptExecutorImpl.doExecuteMain(ScriptExecutorImpl.java:118)
	at java.base/java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:646)
	... 4 common frames omitted

I’m not sure if it’s possible to get beyond this…

Log browser approach

There exists an application called “Log Browser” that displays all the logs.

I had high hopes that I could copy it’s approach to be able to access the logs, but it appears it uses the log files on the server, and not a programmatic logger interface. It actually reads the “logback.xml”-file to create its own logging context.

This will be too lossy for our purposes, as we would need to deserialize the string in the file again. I think it also might be a performance issue to have an application running reading a file all the time.


Possible solutions

  1. Someone tells me what I need to do to get past my issue in the stack trace above
  2. Someone tells me that there already exists an interface/Java-service that I can use to get access to the root-logger or log-stream.
  3. XP exposes a new Java-service (in a new version of XP) that returns the root ch.qos.logback.classic.Logger object (Then I can hopefully register a SentryAppender programmatically).
  4. XP exposes another interface to get access to the stream from the logs
  5. Enonic Cloud re-opens the possibility to add jars to the classpath of XP so that we can start using the Sentry Logback integration again.

Best regards,
– Tom Arild

Hi Tom, and thanks for reaching out.

Our Enterprise Cloud customers enjoy log shipping as an included feature. We support 50+ destinations for log shipping, including AWS, Azure, Google, Splunk, Datadog, New Relic+++. The complete list is available here: Sinks reference | Vector documentation. Sentry however is not on the list. More on this below.

Pro level customers can also get access to log shipping as an add-on.

Sentry is different from other generic log aggregators as it does not accept generic logs, but focuses on structured application error and event handling, normally shipped via their SDK - which must be bundled within your application.

We would love to see a generic “Sentry app” on Enonic Market - but there is no easy way for XP applications to hook into the low level logs at the moment.

Bundling the Sentry SDK with our platform would mean manipulating the standard Docker image. For security, stability, performance, maintenance and debugging reasons, we do not augment our standard Docker images, and certainly not for Pro plan customers. We are open to discussing possible workarounds, and will reach out via DM.

FYI: XP8 is set to ship with Micrometer, offering metrics in a range of formats. Our long term goal is to standardize on Open Telemetry, but the current tools are not yet fully OSGi compliant.