{"id":9,"date":"2021-08-30T09:02:21","date_gmt":"2021-08-30T07:02:21","guid":{"rendered":"https:\/\/the-bug.de\/?p=9"},"modified":"2021-09-11T08:55:12","modified_gmt":"2021-09-11T06:55:12","slug":"shading","status":"publish","type":"post","link":"https:\/\/the-bug.de\/?p=9","title":{"rendered":"SLF4J und logback im Java agent &#8211; SLF4J und logback mit shading"},"content":{"rendered":"\n<p>Letztens haben wir versucht einen Java agent auszuliefernden. Dieser wird dann in eine andere &#8218;beliebige&#8216; Java Applikation eingebaut.<\/p>\n\n\n\n<p>Wie es so mit Java Applikationen ist, gibt es ja einen bunten Blumenstrau\u00df an Logging Frameworks mit noch mehr Versionen. Unser Java Agent soll aber unabh\u00e4ngig von der Applikation loggen k\u00f6nnen und das am Besten mit eigener Konfiguration. Auch soll die instrumentalisierte Java Applikation nicht durch das Logging des Agents beeinflusst werden.<\/p>\n\n\n\n<p>Das Erste, was mir dazu einf\u00e4llt, um dieses Problem zu l\u00f6sen ist das maven shading plugin. Damit bekomme ich &#8218;mein&#8216; logging Framework in den Agent UND kann verhindern, dass das logging der Java Applikation benutzt wird. <\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-plain\"><code> &lt;plugin&gt;\n     &lt;groupId&gt;org.apache.maven.plugins&lt;\/groupId&gt;\n     &lt;artifactId&gt;maven-shade-plugin&lt;\/artifactId&gt;\n     &lt;executions&gt;\n         &lt;execution&gt;\n             &lt;phase&gt;package&lt;\/phase&gt;\n             &lt;goals&gt;\n                 &lt;goal&gt;shade&lt;\/goal&gt;\n             &lt;\/goals&gt;\n         &lt;\/execution&gt;\n     &lt;\/executions&gt;\n     &lt;configuration&gt;\n         &lt;finalName&gt;agent-shaded&lt;\/finalName&gt;\n         &lt;shadedArtifactAttached&gt;true&lt;\/shadedArtifactAttached&gt;\n         &lt;shadedClassifierName&gt;shaded&lt;\/shadedClassifierName&gt;\n         &lt;createDependencyReducedPom&gt;true&lt;\/createDependencyReducedPom&gt;\n         &lt;relocations&gt;\n             &lt;relocation&gt;\n                 &lt;pattern&gt;org.slf4j&lt;\/pattern&gt;\n                 &lt;shadedPattern&gt;my.package.org.slf4j&lt;\/shadedPattern&gt;\n             &lt;\/relocation&gt;\n             &lt;relocation&gt;\n                 &lt;pattern&gt;ch.qos.logback&lt;\/pattern&gt;\n                 &lt;shadedPattern&gt;my.package.ch.qos.logback&lt;\/shadedPattern&gt;\n             &lt;\/relocation&gt;\n         &lt;\/relocations&gt;\n         &lt;transformers&gt;\n             &lt;transformer implementation=&quot;org.apache.maven.plugins.shade.resource.ManifestResourceTransformer&quot;&gt;\n                 &lt;manifestEntries&gt;\n                     &lt;Premain-Class&gt;my.package.instrumentation.InstrumentationAgent&lt;\/Premain-Class&gt;\n                     &lt;Can-Redefine-Classes&gt;true&lt;\/Can-Redefine-Classes&gt;\n                     &lt;Can-Retransform-Classes&gt;true&lt;\/Can-Retransform-Classes&gt;\n                 &lt;\/manifestEntries&gt;\n             &lt;\/transformer&gt;\n         &lt;\/transformers&gt;\n     &lt;\/configuration&gt;\n &lt;\/plugin&gt;<\/code><\/pre><\/div>\n\n\n\n<p>Das mit der Relocation klappt schon ganz gut. Das Logback, dass im Agent ist, kann jetzt nicht mehr von der Java Applikation angesprochen werden, da es ja jetzt unter <em>my.package.ch.qos.logback<\/em> liegt und nicht mehr unter <em>ch.qos.logback<\/em>. Das Selbe gilt f\u00fcr slf4j.<\/p>\n\n\n\n<p>Aber jetzt kommt die B\u00d6SE Konfiguration:<br>Wenn die Java Anwendung mit den Agent gestartet wird und die Java Anwendung <strong>kein<\/strong> logback benutzt, dann ist alles ganz einfach. Mann benutzt die normale Logback Konfiguration und passt die Pfade auf das Relocation an:<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-plain\"><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;\n&lt;configuration&gt;\n    &lt;appender name=&quot;OPS_FILE&quot; class=&quot;my.package.ch.qos.logback.core.rolling.RollingFileAppender&quot;&gt;\n        &lt;file&gt;agent.log&lt;\/file&gt;\n        &lt;rollingPolicy class=&quot;my.package.ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy&quot;&gt;\n            &lt;fileNamePattern&gt;agent.%d{yyyy-MM-dd}.%i.gz&lt;\/fileNamePattern&gt;\n            &lt;maxFileSize&gt;20MB&lt;\/maxFileSize&gt;\n            &lt;maxHistory&gt;30&lt;\/maxHistory&gt;\n            &lt;totalSizeCap&gt;1GB&lt;\/totalSizeCap&gt;\n        &lt;\/rollingPolicy&gt;\n        &lt;append&gt;true&lt;\/append&gt;\n        &lt;immediateFlush&gt;true&lt;\/immediateFlush&gt;\n        &lt;encoder&gt;\n            &lt;pattern&gt;%d{yyyy-MM-dd HH:mm:ss,SSS} %-5level %thread [%c{15}] %msg%ex%n&lt;\/pattern&gt;\n        &lt;\/encoder&gt;\n    &lt;\/appender&gt;\n    &lt;root level=&quot;DEBUG&quot;&gt;\n        &lt;appender-ref ref=&quot;OPS_FILE&quot;\/&gt;\n    &lt;\/root&gt;\n&lt;\/configuration&gt;\n<\/code><\/pre><\/div>\n\n\n\n<p>Benutzt die Java Applikation aber jetzt selber logback, dann bekommt man mit der Konfigurationsaufl\u00f6sung von logback Probleme:<br>Benutzt die Anwendung z.B. eine <em>logback.xml<\/em> auf dem Classpath, dann bekommt das auch logback im Agent mit.<br>Da die Klassen durch das shading anders hei\u00dfen fliegen beim Aufl\u00f6sen der Konfiguration im Agent Fehlermeldungen, die unsch\u00f6n sind.<\/p>\n\n\n\n<p>Das <em>Problem<\/em> bzw. die L\u00f6sung liegt in der Auto Konfiguration in der Methode ch.qos.logback.classic.util.ContextInitializer. Man kann hier leider nicht eingreifen, da hier alles \u00fcber Konvention automatisch l\u00e4uft. Siehe auch http:\/\/logback.qos.ch\/manual\/configuration.html.<\/p>\n\n\n\n<p>Jetzt k\u00f6nnte man sich die logback forken, die Klasse anpassen und sein eigenes logback benutzten. Aber wenn man schon Java Agent benutzt, kann man auch f\u00fcr dieses Problem anpassen.<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-plain\"><code>import java.lang.instrument.ClassFileTransformer;\nimport java.lang.instrument.IllegalClassFormatException;\nimport java.security.ProtectionDomain;\n\nimport javassist.ClassPool;\nimport javassist.CtClass;\nimport javassist.CtMethod;\n\npublic class LoggingTransformer implements ClassFileTransformer {\n\n    @Override\n     public byte[] transform(ClassLoader loader, String className, Class&lt;?&gt; classBeingRedefined,\n            ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {\n        byte[] byteCode = classfileBuffer;\n\n        if (className.equals(&quot;my\/package\/ch\/qos\/logback\/classic\/util\/ContextInitializer&quot;)) {\n            try {\n                ClassPool cp = ClassPool.getDefault();\n                CtClass cc = cp.get(&quot;my.package.ch.qos.logback.classic.util.ContextInitializer&quot;);\n                CtMethod m = cc.getDeclaredMethod(&quot;autoConfig&quot;);\n                \/\/ If we return null here we skip the default configuration file resolution.\n                \/\/ Thus we have the BasicConfigurator.\n                m.setBody(&quot;{return null;}&quot;);\n                byteCode = cc.toBytecode();\n                cc.detach();\n            } catch (Exception ex) {\n                ex.printStackTrace();\n            }\n        }\n        return byteCode;\n    }\n}<\/code><\/pre><\/div>\n\n\n\n<p>Somit hat man die komplette Autokonfiguration von logback zerst\u00f6rt und man kann seine eigene Konfiguration \u00fcbergeben. z.B. \u00fcber ein Konfigurationsfile:<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-plain\"><code>        JoranConfigurator configurator = new JoranConfigurator();\n\n        LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();\n        configurator.setContext(context);\n\n        String pathToLogbackConfig = System.getProperty(&quot;agent.logback.configurationFile&quot;);\n        if (pathToLogbackConfig == null) {\n            System.out.println(&quot;There was no configuration for logging of the agent provided.&quot;);\n            return;\n        }\n\n        try {\n            File file = new File(pathToLogbackConfig);\n            if (file.exists()) {\n                context.reset();\n                configurator.doConfigure(file);\n            } else {\n                System.err.println(\n                        &quot;There was no &#39;agent-logback.xml&#39; logging-configuration provided. This means all logging of the agent is lost!&quot;);\n            }\n        } catch (JoranException e) {\n            System.err.println(&quot;There was an error during building the agent logging configuration.&quot;);\n            System.out.println(e);\n        }<\/code><\/pre><\/div>\n\n\n\n<p>Jetzt kann man den Agent nach Belieben Konfigurieren und das logging der Java Applikation und des Agent ist komplett getrennt.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Letztens haben wir versucht einen Java agent auszuliefernden. Dieser wird dann in eine andere &#8218;beliebige&#8216; Java Applikation eingebaut. Wie es so mit Java Applikationen ist, gibt es ja einen bunten Blumenstrau\u00df an Logging Frameworks mit noch mehr Versionen. Unser Java Agent soll aber unabh\u00e4ngig von der Applikation loggen k\u00f6nnen und das am Besten mit eigener [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5,3,4,6],"tags":[],"class_list":["post-9","post","type-post","status-publish","format-standard","hentry","category-agent","category-informatics","category-java","category-logging","post-preview"],"_links":{"self":[{"href":"https:\/\/the-bug.de\/index.php?rest_route=\/wp\/v2\/posts\/9","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/the-bug.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/the-bug.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/the-bug.de\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/the-bug.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=9"}],"version-history":[{"count":10,"href":"https:\/\/the-bug.de\/index.php?rest_route=\/wp\/v2\/posts\/9\/revisions"}],"predecessor-version":[{"id":34,"href":"https:\/\/the-bug.de\/index.php?rest_route=\/wp\/v2\/posts\/9\/revisions\/34"}],"wp:attachment":[{"href":"https:\/\/the-bug.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=9"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/the-bug.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=9"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/the-bug.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=9"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}