You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by dk...@apache.org on 2022/02/03 21:31:36 UTC

[sling-whiteboard] 01/02: SLING-11104 - POC of OSGi configurable JSON logging implementation based on Logstash, Felix Logback but still supporting OSGi configurations for log levels

This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-whiteboard.git

commit 3fcab98f829919b4854b12420e2ebfd2b629787b
Author: Dan Klco <kl...@adobe.com>
AuthorDate: Thu Feb 3 16:28:02 2022 -0500

    SLING-11104 - POC of OSGi configurable JSON logging implementation based on Logstash, Felix Logback but still supporting OSGi configurations for log levels
---
 .../org.apache.sling.commons.log.json/pom.xml      | 144 +++++++++++++
 .../log/json/impl/LogFileSourceJsonProvider.java   |  64 ++++++
 .../sling/commons/log/json/impl/LogManager.java    |  60 ++++++
 .../commons/log/json/impl/LoggerConfiguration.java |  77 +++++++
 .../sling/commons/log/json/impl/LoggerPrinter.java |  97 +++++++++
 .../commons/log/json/impl/OsgiConfigurator.java    | 239 +++++++++++++++++++++
 .../resources/OSGI-INF/l10n/metatype.properties    |  71 ++++++
 .../log/json/impl/LoggerConfigurationTest.java     |  69 ++++++
 .../commons/log/json/impl/LoggerPrinterTest.java   |  73 +++++++
 .../log/json/impl/OsgiConfiguratorTest.java        |  92 ++++++++
 .../sling/commons/log/json/impl/TestHelper.java    | 148 +++++++++++++
 .../src/test/resources/logback.xml                 |  28 +++
 .../bnd.bnd                                        |   2 +
 .../pom.xml                                        |  98 +++++++++
 .../commons/log/compat/StartupConfigurator.java    |  65 ++++++
 .../ch.qos.logback.classic.spi.Configurator        |   1 +
 16 files changed, 1328 insertions(+)

diff --git a/json-logs/org.apache.sling.commons.log.json/pom.xml b/json-logs/org.apache.sling.commons.log.json/pom.xml
new file mode 100644
index 0000000..4beeac1
--- /dev/null
+++ b/json-logs/org.apache.sling.commons.log.json/pom.xml
@@ -0,0 +1,144 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling-bundle-parent</artifactId>
+        <version>46</version>
+        <relativePath />
+    </parent>
+
+    <artifactId>org.apache.sling.commons.log.json</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+    <name>Apache Sling - Commons Log JSON</name>
+    <description>Supports generating JSON logs to the console and reading Sling Commons Logging configurations for configurable logger levels</description>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.build.outputTimestamp>10</project.build.outputTimestamp>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>biz.aQute.bnd</groupId>
+                <artifactId>bnd-baseline-maven-plugin</artifactId>
+                <configuration>
+                    <failOnMissing>false</failOnMissing>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>src/main/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <version>1.2.10</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>net.logstash.logback</groupId>
+            <artifactId>logstash-logback-encoder</artifactId>
+            <version>7.0.1</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.jetbrains</groupId>
+            <artifactId>annotations</artifactId>
+            <version>23.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+
+
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.core</artifactId>
+            <version>7.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.cmpn</artifactId>
+            <version>7.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.annotation</artifactId>
+            <version>8.0.1</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.component.annotations</artifactId>
+            <version>1.4.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.metatype.annotations</artifactId>
+            <version>1.4.1</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.cm</artifactId>
+            <version>1.6.1</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.10.19</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.testing.osgi-mock.junit4</artifactId>
+            <version>3.2.2</version>
+            <scope>test</scope>
+        </dependency>
+
+
+    </dependencies>
+</project>
\ No newline at end of file
diff --git a/json-logs/org.apache.sling.commons.log.json/src/main/java/org/apache/sling/commons/log/json/impl/LogFileSourceJsonProvider.java b/json-logs/org.apache.sling.commons.log.json/src/main/java/org/apache/sling/commons/log/json/impl/LogFileSourceJsonProvider.java
new file mode 100644
index 0000000..2598a55
--- /dev/null
+++ b/json-logs/org.apache.sling.commons.log.json/src/main/java/org/apache/sling/commons/log/json/impl/LogFileSourceJsonProvider.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.commons.log.json.impl;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import net.logstash.logback.composite.AbstractJsonProvider;
+
+/**
+ * Appends the property org.apache.sling.commons.log.file to loggers based on
+ * the custom logger mappings.
+ */
+public class LogFileSourceJsonProvider extends AbstractJsonProvider<ILoggingEvent> {
+
+    private Map<String, String> loggersToFiles = Collections.emptyMap();
+
+    public void setAttachedLoggers(Map<LoggerConfiguration, List<Logger>> attachedLoggers) {
+        addInfo("Updating attached providers");
+        Map<String, String> newMapping = new HashMap<>();
+        attachedLoggers.entrySet().forEach(al -> al.getValue().stream().map(Logger::getName)
+                .forEach(l -> newMapping.put(l, al.getKey().getConfig().org_apache_sling_commons_log_file())));
+        loggersToFiles = Collections.unmodifiableMap(newMapping);
+        addInfo("Attached providers updated");
+    }
+
+    @Override
+    public void writeTo(JsonGenerator generator, ILoggingEvent event) throws IOException {
+        if (isStarted()) {
+            Optional<String> logFile = loggersToFiles.entrySet().stream()
+                    .filter(e -> event.getLoggerName().startsWith(e.getKey())).findAny()
+                    .map(Entry::getValue);
+            if (logFile.isPresent()) {
+                generator.writeStringField("org.apache.sling.commons.log.file", logFile.get());
+            }
+        }
+    }
+
+}
diff --git a/json-logs/org.apache.sling.commons.log.json/src/main/java/org/apache/sling/commons/log/json/impl/LogManager.java b/json-logs/org.apache.sling.commons.log.json/src/main/java/org/apache/sling/commons/log/json/impl/LogManager.java
new file mode 100644
index 0000000..0a44899
--- /dev/null
+++ b/json-logs/org.apache.sling.commons.log.json/src/main/java/org/apache/sling/commons/log/json/impl/LogManager.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.commons.log.json.impl;
+
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.osgi.service.metatype.annotations.Option;
+
+@Component(immediate = true, service = LogManager.class, name = "org.apache.sling.commons.log.LogManager")
+@Designate(ocd = OsgiConfigurator.Config.class)
+public class LogManager {
+
+    private final Config config;
+
+    @Activate
+    public LogManager(Config config) {
+        this.config = config;
+    }
+
+    public Config getConfig() {
+        return config;
+    }
+
+    @ObjectClassDefinition(name = "%log.name", description = "%log.description", localization = "OSGI-INF/l10n/metatype")
+    public @interface Config {
+        @AttributeDefinition(name = "%log.level.name", description = "%log.level.description", options = {
+                @Option(value = "off", label = "Off"),
+                @Option(value = "trace", label = "Trace"),
+                @Option(value = "debug", label = "Debug"),
+                @Option(value = "info", label = "Information"),
+                @Option(value = "warn", label = "Warnings"),
+                @Option(value = "error", label = "Error") })
+        String org_apache_sling_commons_log_level() default "info";
+
+        @AttributeDefinition(name = "%log.config.maxCallerDataDepth.name", description = "%log.config.maxCallerDataDepth.description")
+        int org_apache_sling_commons_log_maxCallerDataDepth() default 7;
+
+        @AttributeDefinition(name = "%log.config.packagingData.name", description = "%log.config.packagingData.description")
+        boolean org_apache_sling_commons_log_packagingDataEnabled() default true;
+    }
+}
diff --git a/json-logs/org.apache.sling.commons.log.json/src/main/java/org/apache/sling/commons/log/json/impl/LoggerConfiguration.java b/json-logs/org.apache.sling.commons.log.json/src/main/java/org/apache/sling/commons/log/json/impl/LoggerConfiguration.java
new file mode 100644
index 0000000..a3b9fae
--- /dev/null
+++ b/json-logs/org.apache.sling.commons.log.json/src/main/java/org/apache/sling/commons/log/json/impl/LoggerConfiguration.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.commons.log.json.impl;
+
+import java.util.Optional;
+
+import org.osgi.framework.Constants;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.osgi.service.metatype.annotations.Option;
+
+/**
+ * OSGi Service for loading logger-specific configurations
+ */
+@Component(name = "org.apache.sling.commons.log.LogManager.factory.config", service = LoggerConfiguration.class)
+@Designate(ocd = LoggerConfiguration.Config.class, factory = true)
+public class LoggerConfiguration {
+
+    private final Config config;
+    private final String pid;
+
+    @Activate
+    public LoggerConfiguration(Config config, ComponentContext componentContext) {
+        this.config = config;
+        pid = Optional.ofNullable(componentContext.getProperties().get(Constants.SERVICE_PID)).map(String.class::cast)
+                .orElse(getClass().getName() + "@" + System.identityHashCode(this));
+    }
+
+    public Config getConfig() {
+        return config;
+    }
+
+    public String getPid() {
+        return pid;
+    }
+
+    @ObjectClassDefinition(name = "%log.factory.config.name", description = "%log.factory.config.description", localization = "OSGI-INF/l10n/metatype")
+    public @interface Config {
+
+        @AttributeDefinition(name = "%log.file.name", description = "%log.file.description")
+        public String org_apache_sling_commons_log_file() default "logs/errorlog";
+
+        @AttributeDefinition(name = "%log.level.name", description = "%log.level.description", options = {
+                @Option(value = "off", label = "Off"),
+                @Option(value = "trace", label = "Trace"),
+                @Option(value = "debug", label = "Debug"),
+                @Option(value = "info", label = "Information"),
+                @Option(value = "warn", label = "Warnings"),
+                @Option(value = "error", label = "Error") })
+        public String org_apache_sling_commons_log_level() default "info";
+
+        @AttributeDefinition(name = "%log.loggers.name", description = "%log.loggers.description")
+        public String[] org_apache_sling_commons_log_names();
+
+    }
+
+}
diff --git a/json-logs/org.apache.sling.commons.log.json/src/main/java/org/apache/sling/commons/log/json/impl/LoggerPrinter.java b/json-logs/org.apache.sling.commons.log.json/src/main/java/org/apache/sling/commons/log/json/impl/LoggerPrinter.java
new file mode 100644
index 0000000..eb71851
--- /dev/null
+++ b/json-logs/org.apache.sling.commons.log.json/src/main/java/org/apache/sling/commons/log/json/impl/LoggerPrinter.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.commons.log.json.impl;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.core.BasicStatusManager;
+import ch.qos.logback.core.status.Status;
+import ch.qos.logback.core.util.StatusPrinter;
+
+/**
+ * Console plugin for printing the logback status and registered custom loggers
+ */
+@Component(property = {
+        "felix.webconsole.label=log_json",
+        "felix.webconsole.title=" + LoggerPrinter.HEADLINE,
+        "felix.webconsole.configprinter.modes=always",
+
+}, service = LoggerPrinter.class)
+public class LoggerPrinter extends BasicStatusManager {
+
+    private final LoggerContext loggerContext;
+    private final OsgiConfigurator configurator;
+
+    static final String HEADLINE = "Sling Commons Log - JSON";
+
+    @Activate
+    public LoggerPrinter(@Reference OsgiConfigurator configurator) {
+        loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
+        loggerContext.getStatusManager().getCopyOfStatusList().forEach(this::add);
+        loggerContext.setStatusManager(this);
+        this.configurator = configurator;
+    }
+
+    private void renderHeader(PrintWriter pw, String header) {
+        pw.println("\n\n" + header + "\n-------------------\n");
+    }
+
+    private String statusToString(Status status) {
+        StringBuilder sb = new StringBuilder();
+        StatusPrinter.buildStr(sb, "", status);
+        return sb.toString();
+    }
+
+    /**
+     * Print out the logging status
+     * 
+     * @see org.apache.felix.webconsole.ConfigurationPrinter#printConfiguration(java.io.PrintWriter)
+     */
+    public void printConfiguration(PrintWriter pw) {
+
+        pw.println(HEADLINE + "\n===========================");
+
+        renderHeader(pw, "Status");
+        this.getCopyOfStatusList().stream().map(this::statusToString).forEach(pw::println);
+
+        renderHeader(pw, "Attached Loggers");
+        configurator.getAttachedLoggers().entrySet().forEach(e -> {
+            renderHeader(pw, e.getKey().getPid());
+            pw.println("Log File: " + e.getKey().getConfig().org_apache_sling_commons_log_file());
+            pw.println("Log Log Level: " + e.getKey().getConfig().org_apache_sling_commons_log_level());
+            pw.println("Defined Logger Names: "
+                    + Arrays.stream(e.getKey().getConfig().org_apache_sling_commons_log_names())
+                            .collect(Collectors.joining(",")));
+            pw.println(
+                    "Attached Logger Names: "
+                            + e.getValue().stream().map(Logger::getName).collect(Collectors.joining(",")));
+        });
+
+    }
+
+}
diff --git a/json-logs/org.apache.sling.commons.log.json/src/main/java/org/apache/sling/commons/log/json/impl/OsgiConfigurator.java b/json-logs/org.apache.sling.commons.log.json/src/main/java/org/apache/sling/commons/log/json/impl/OsgiConfigurator.java
new file mode 100644
index 0000000..45a6268
--- /dev/null
+++ b/json-logs/org.apache.sling.commons.log.json/src/main/java/org/apache/sling/commons/log/json/impl/OsgiConfigurator.java
@@ -0,0 +1,239 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.commons.log.json.impl;
+
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.Appender;
+import ch.qos.logback.core.ConsoleAppender;
+import ch.qos.logback.core.OutputStreamAppender;
+import ch.qos.logback.core.encoder.Encoder;
+import ch.qos.logback.core.spi.ContextAwareBase;
+import net.logstash.logback.encoder.LogstashEncoder;
+
+/**
+ * OSGi service for loading the root Sling Commons Log configuration and
+ * registering the custom logger configurations.
+ * 
+ * Note this requires the root logger of the Logaback context to have an
+ * appender of type {@link ConsoleAppender} with the name "console" which has
+ * it's encoder set as an {@link LogstashEncoder}
+ */
+@Component(immediate = true, service = OsgiConfigurator.class)
+@Designate(ocd = OsgiConfigurator.Config.class)
+public class OsgiConfigurator extends ContextAwareBase {
+
+    private final List<LoggerConfiguration> queuedConfigurations = new CopyOnWriteArrayList<>();
+    private final Set<String> loggerNames = Collections
+            .newSetFromMap(new ConcurrentHashMap<String, Boolean>());
+    private final Map<LoggerConfiguration, List<Logger>> attachedLoggers = new ConcurrentHashMap<>();
+
+    private Config config;
+    private LoggerContext loggerContext;
+    private OutputStreamAppender<ILoggingEvent> appender;
+    private Instant start;
+    private ScheduledFuture<?> future;
+    private boolean initialized = false;
+    private LogManager logManager;
+
+    private LogFileSourceJsonProvider logFileSourceProvider = new LogFileSourceJsonProvider();
+
+    @Activate
+    public void activate(Config config) {
+        this.config = config;
+        start = Instant.now();
+        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
+        future = scheduler.scheduleWithFixedDelay(this::initialize, 1, 1, TimeUnit.MILLISECONDS);
+    }
+
+    @Deactivate
+    public void deactivate() {
+        if (loggerContext != null) {
+            loggerContext.stop();
+        }
+    }
+
+    private void initialize() {
+        if (isSlf4jInitialized()) {
+            future.cancel(false);
+
+            loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
+            this.setContext(loggerContext);
+
+            addInfo("OsgiConfigurator started at " + start.toEpochMilli());
+            addInfo("Initialization started after " + (Instant.now().toEpochMilli() - start.toEpochMilli()) + "ms");
+
+            addInfo("Initializing OSGi Configuration");
+            Logger rootLogger = loggerContext.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
+            applyLogManagerConfiguration(rootLogger);
+
+            Appender<ILoggingEvent> foundAppender = rootLogger.getAppender(config.appenderName());
+            if (!(foundAppender instanceof OutputStreamAppender<?>)) {
+                addError("Did not find expected appender: " + config.appenderName());
+                return;
+            }
+            this.appender = (OutputStreamAppender<ILoggingEvent>) foundAppender;
+
+            Encoder<?> foundEncoder = appender.getEncoder();
+            if (!(foundEncoder instanceof LogstashEncoder)) {
+                addWarn("Did not find expected logstash encoder");
+            } else {
+                LogstashEncoder encoder = (LogstashEncoder) foundEncoder;
+                logFileSourceProvider.setContext(context);
+                logFileSourceProvider.start();
+                encoder.addProvider(logFileSourceProvider);
+            }
+            addInfo("OSGi initialization complete");
+            initialized = true;
+
+            addInfo("Attaching " + queuedConfigurations.size() + " queued configurations");
+            queuedConfigurations.forEach(this::attachLogger);
+            queuedConfigurations.clear();
+        }
+    }
+
+    private void applyLogManagerConfiguration(Logger rootLogger) {
+        addInfo("Applying log manager configuration");
+        loggerContext.setMaxCallerDataDepth(logManager.getConfig().org_apache_sling_commons_log_maxCallerDataDepth());
+        loggerContext
+                .setPackagingDataEnabled(logManager.getConfig().org_apache_sling_commons_log_packagingDataEnabled());
+        rootLogger.setLevel(Level.valueOf(logManager.getConfig().org_apache_sling_commons_log_level()));
+        addInfo("Log manager configuration applied");
+    }
+
+    private static boolean isSlf4jInitialized() {
+        return LoggerFactory.getILoggerFactory() instanceof LoggerContext;
+    }
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY)
+    protected void bindLogManager(LogManager logManager) {
+        if (!isInitialized()) {
+            this.logManager = logManager;
+        } else {
+            this.logManager = logManager;
+            applyLogManagerConfiguration(loggerContext.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME));
+        }
+    }
+
+    protected void unbindLogManager(LogManager logManager) {
+        // nothing needs done, we'll wait until a new one is provided
+    }
+
+    @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY)
+    protected void bindLoggerConfiguration(LoggerConfiguration config) {
+        if (!isInitialized()) {
+            queuedConfigurations.add(config);
+        } else {
+            attachLogger(config);
+        }
+    }
+
+    private void attachLogger(LoggerConfiguration config) {
+        List<Logger> loggers = Arrays.stream(config.getConfig().org_apache_sling_commons_log_names())
+                .filter(n -> !loggerNames.contains(n)).map(loggerName -> {
+                    addInfo("Adding logger: " + loggerName + " at level "
+                            + config.getConfig().org_apache_sling_commons_log_level() + " from config: "
+                            + config.getPid());
+                    Logger logger = loggerContext.getLogger(loggerName);
+                    logger.setLevel(Level.valueOf(config.getConfig().org_apache_sling_commons_log_level()));
+                    logger.addAppender(appender);
+                    loggerNames.add(loggerName);
+                    return logger;
+                }).collect(Collectors.toList());
+        attachedLoggers.put(config, loggers);
+        logFileSourceProvider.setAttachedLoggers(attachedLoggers);
+    }
+
+    protected void unbindLoggerConfiguration(LoggerConfiguration config) {
+        if (!isInitialized()) {
+            queuedConfigurations.remove(config);
+        } else {
+            Optional.ofNullable(attachedLoggers.remove(config)).orElse(Collections.emptyList()).forEach(logger -> {
+                addInfo("Removing logger: " + logger + " provided by config: "
+                        + config.getConfig().getClass().getName());
+                loggerNames.remove(logger.getName());
+                logger.detachAndStopAllAppenders();
+            });
+            logFileSourceProvider.setAttachedLoggers(attachedLoggers);
+        }
+    }
+
+    /**
+     * @return the initialized
+     */
+    public boolean isInitialized() {
+        return initialized;
+    }
+
+    /**
+     * @return the attachedLoggers
+     */
+    public Map<LoggerConfiguration, List<Logger>> getAttachedLoggers() {
+        return attachedLoggers;
+    }
+
+    /**
+     * @return the config
+     */
+    public Config getConfig() {
+        return config;
+    }
+
+    /**
+     * @param config the config to set
+     */
+    public void setConfig(Config config) {
+        this.config = config;
+    }
+
+    @ObjectClassDefinition(name = "%log.json.name", description = "%log.json.description", localization = "OSGI-INF/l10n/metatype")
+    public @interface Config {
+        @AttributeDefinition(name = "%appenderName.name", description = "%appenderName.description")
+        String appenderName() default "console";
+    }
+}
diff --git a/json-logs/org.apache.sling.commons.log.json/src/main/resources/OSGI-INF/l10n/metatype.properties b/json-logs/org.apache.sling.commons.log.json/src/main/resources/OSGI-INF/l10n/metatype.properties
new file mode 100644
index 0000000..32eb37a
--- /dev/null
+++ b/json-logs/org.apache.sling.commons.log.json/src/main/resources/OSGI-INF/l10n/metatype.properties
@@ -0,0 +1,71 @@
+#
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
+#
+
+log.name = Apache Sling Logging Configuration
+log.description = Global configuration setup for the Sling OSGi LogService \
+ implementation. See http://sling.apache.org/site/logging.html for more \
+ detailed documentation and description.
+
+
+log.json.name = Apache Sling JSON Logging Configuration
+log.json.description = Configuration for the Sling JSON OSGi LogService \
+ See http://sling.apache.org/site/logging.html for more \
+ detailed documentation and description.
+
+log.level.name = Log Level
+log.level.description = Root Logger log level setting.
+log.file.name = Log File
+log.file.description = The name and path of the log file. If this is empty, \
+ logging goes to standard output (the console). If this path is relative it \
+ is resolved below ${sling.home}.
+ 
+log.factory.config.name = Apache Sling Logging Logger Configuration
+log.factory.config.description = Configure Loggers with levels, pattern and \
+ destination. See http://sling.apache.org/site/logging.html for more detailed \
+ documentation and description.
+ 
+log.loggers.name = Logger
+log.loggers.description = The logger names applicable for this logger \
+ configuration. Each logger name applies for any child category unless configured \
+ otherwise. E.g. a logger name of org.apache.sling applies to logger \
+ org.apache.sling.commons unless there is a different configuration for \
+ org.apache.sling.commons.
+
+log.config.packagingData.name = Packaging Data
+log.config.packagingData.description = Include the packaging data which provide \
+  details about jar name and version of jar from which the class is loaded as part \
+  of stacktrace. See http://logback.qos.ch/reasonsToSwitch.html#packagingData
+
+log.config.maxCallerDataDepth.name = Max Caller Depth
+log.config.maxCallerDataDepth.description = The stack data depth computed during caller data \
+  extraction. See http://logback.qos.ch/manual/layouts.html#caller
+
+log.file.buffered.name = Buffered Logging
+log.file.buffered.description = By default logging events are immediately written to disk \
+  and will not be lost in case your application exits without properly closing appenders. \
+  If set to true  and if appenders are not closed properly when your application exits, then \
+  logging events not yet written to disk may be lost. See http://logback.qos.ch/manual/encoders.html#immediateFlush
+
+log.additiv.name = Additivity
+log.additiv.description = If set to false then logs from these loggers would not be sent \
+   to any appender attached higher in the hierarchy
+
+
+appenderName.name=Appender Name
+appenderName.description=The name of the Logback appender to which to attach the configurations
\ No newline at end of file
diff --git a/json-logs/org.apache.sling.commons.log.json/src/test/java/org/apache/sling/commons/log/json/impl/LoggerConfigurationTest.java b/json-logs/org.apache.sling.commons.log.json/src/test/java/org/apache/sling/commons/log/json/impl/LoggerConfigurationTest.java
new file mode 100644
index 0000000..371bf52
--- /dev/null
+++ b/json-logs/org.apache.sling.commons.log.json/src/test/java/org/apache/sling/commons/log/json/impl/LoggerConfigurationTest.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.commons.log.json.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.Constants;
+import org.osgi.service.component.ComponentContext;
+
+public class LoggerConfigurationTest {
+
+    private ComponentContext componentContext;
+    private Dictionary<String, Object> properties;
+
+    @Before
+    public void init() {
+        componentContext = mock(ComponentContext.class);
+        properties = new Hashtable<>();
+        when(componentContext.getProperties()).thenReturn(properties);
+    }
+
+    @Test
+    public void testLoggerConfiguration() {
+        String expectedPid = "test";
+        properties.put(Constants.SERVICE_PID, expectedPid);
+        LoggerConfiguration.Config config = TestHelper.createLoggerConfigurationConfig("logs/test.log", "info",
+                new String[] { "com.text" });
+        LoggerConfiguration configuration = new LoggerConfiguration(config, componentContext);
+
+        assertEquals(expectedPid, configuration.getPid());
+        assertEquals(config, configuration.getConfig());
+    }
+
+    @Test
+    public void testNoPid() {
+        LoggerConfiguration.Config config = TestHelper.createLoggerConfigurationConfig("logs/test.log", "info",
+                new String[] { "com.text" });
+        LoggerConfiguration configuration = new LoggerConfiguration(config, componentContext);
+
+        assertNotNull(configuration.getPid());
+        assertTrue(configuration.getPid().startsWith(LoggerConfiguration.class.getName() + "@"));
+        assertEquals(config, configuration.getConfig());
+    }
+}
diff --git a/json-logs/org.apache.sling.commons.log.json/src/test/java/org/apache/sling/commons/log/json/impl/LoggerPrinterTest.java b/json-logs/org.apache.sling.commons.log.json/src/test/java/org/apache/sling/commons/log/json/impl/LoggerPrinterTest.java
new file mode 100644
index 0000000..4c403f8
--- /dev/null
+++ b/json-logs/org.apache.sling.commons.log.json/src/test/java/org/apache/sling/commons/log/json/impl/LoggerPrinterTest.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.commons.log.json.impl;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.classic.LoggerContext;
+
+public class LoggerPrinterTest {
+
+    private OsgiConfigurator osgiConfig;
+
+    @Before
+    public void init() {
+        osgiConfig = TestHelper.mockOsgiConfigurator("info");
+        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
+        loggerContext.start();
+    }
+
+    @After
+    public void reset() {
+        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
+        loggerContext.reset();
+    }
+
+    @Test
+    public void testPrinter() {
+        LoggerConfiguration loggerConfig = TestHelper.mockLoggerConfiguration("logs/test.log", "debug",
+                new String[] { "com.text" });
+        osgiConfig.bindLoggerConfiguration(loggerConfig);
+
+        LoggerPrinter printer = new LoggerPrinter(osgiConfig);
+
+        StringWriter sw = new StringWriter();
+        printer.printConfiguration(new PrintWriter(sw));
+
+        String written = sw.toString();
+
+        assertTrue("Does not contain console header",
+                written.contains(String.format("Sling Commons Log - JSON%n===========================")));
+        assertTrue("Does not contain status header",
+                written.contains(String.format("Status%n-------------------")));
+        assertTrue("Does not contain attached loggers header",
+                written.contains(String.format("Attached Loggers%n-------------------")));
+        assertTrue("Does not contain attached loggers",
+                written.contains(String.format(
+                        "org.apache.sling.commons.log.json.impl.LoggerConfiguration~logs/test.log%n-------------------%n%nLog File: logs/test.log%nLog Log Level: debug%nDefined Logger Names: com.text%nAttached Logger Names: com.text")));
+    }
+}
diff --git a/json-logs/org.apache.sling.commons.log.json/src/test/java/org/apache/sling/commons/log/json/impl/OsgiConfiguratorTest.java b/json-logs/org.apache.sling.commons.log.json/src/test/java/org/apache/sling/commons/log/json/impl/OsgiConfiguratorTest.java
new file mode 100644
index 0000000..4ca0c13
--- /dev/null
+++ b/json-logs/org.apache.sling.commons.log.json/src/test/java/org/apache/sling/commons/log/json/impl/OsgiConfiguratorTest.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.commons.log.json.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+
+public class OsgiConfiguratorTest {
+
+    @Rule
+    public final OsgiContext context = new OsgiContext();
+
+    @Before
+    public void init() {
+        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
+        loggerContext.start();
+    }
+
+    @After
+    public void reset() {
+        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
+        loggerContext.reset();
+    }
+
+    @Test
+    public void tes() throws InterruptedException {
+
+        Map<String, Object> loggerConfiguration = new HashMap<>();
+        loggerConfiguration.put("org.apache.sling.commons.log.level", "debug");
+        loggerConfiguration.put("org.apache.sling.commons.log.file", "logs/test.log");
+        loggerConfiguration.put("org.apache.sling.commons.log.names", new String[] { "org.apache.sling" });
+        context.registerInjectActivateService(LoggerConfiguration.class,
+                loggerConfiguration);
+
+        context.registerInjectActivateService(LogManager.class,
+                Collections.singletonMap("org.apache.sling.commons.log.level", "warn"));
+
+        context.registerInjectActivateService(OsgiConfigurator.class, new OsgiConfigurator(),
+                Collections.singletonMap("appenderName", "console"));
+
+        OsgiConfigurator configurator = context.getService(OsgiConfigurator.class);
+
+        TimeUnit.SECONDS.sleep(1);
+
+        Map<String, Object> loggerConfiguration2 = new HashMap<>();
+        loggerConfiguration.put("org.apache.sling.commons.log.level", "debug");
+        loggerConfiguration.put("org.apache.sling.commons.log.file", "logs/another.log");
+        loggerConfiguration.put("org.apache.sling.commons.log.names", new String[] { "org.apache.felix" });
+        context.registerInjectActivateService(LoggerConfiguration.class,
+                loggerConfiguration2);
+
+        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
+        Logger rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
+        assertEquals(Level.WARN, rootLogger.getLevel());
+
+        assertTrue(configurator.isInitialized());
+
+        assertEquals(2, configurator.getAttachedLoggers().size());
+    }
+}
diff --git a/json-logs/org.apache.sling.commons.log.json/src/test/java/org/apache/sling/commons/log/json/impl/TestHelper.java b/json-logs/org.apache.sling.commons.log.json/src/test/java/org/apache/sling/commons/log/json/impl/TestHelper.java
new file mode 100644
index 0000000..ff5762c
--- /dev/null
+++ b/json-logs/org.apache.sling.commons.log.json/src/test/java/org/apache/sling/commons/log/json/impl/TestHelper.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.commons.log.json.impl;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.classic.Logger;
+
+public class TestHelper {
+
+    private TestHelper() {
+    };
+
+    public static OsgiConfigurator mockOsgiConfigurator(String appenderName) {
+
+        OsgiConfigurator osgiConfigurator = mock(OsgiConfigurator.class);
+        when(osgiConfigurator.isInitialized()).thenReturn(true);
+
+        OsgiConfigurator.Config config = new OsgiConfigurator.Config() {
+
+            @Override
+            public Class<? extends Annotation> annotationType() {
+                return null;
+            }
+
+            @Override
+            public String appenderName() {
+                return appenderName;
+            }
+
+        };
+
+        when(osgiConfigurator.getConfig()).thenReturn(config);
+
+        Map<LoggerConfiguration, List<Logger>> attachedLoggers = new ConcurrentHashMap<>();
+        doAnswer((inv) -> {
+            LoggerConfiguration lc = inv.getArgumentAt(0, LoggerConfiguration.class);
+            attachedLoggers.put(lc,
+                    Arrays.stream(lc.getConfig().org_apache_sling_commons_log_names()).map(TestHelper::getLogger)
+                            .collect(Collectors.toList()));
+            return null;
+        }).when(osgiConfigurator).bindLoggerConfiguration(any(LoggerConfiguration.class));
+        when(osgiConfigurator.getAttachedLoggers()).thenReturn(attachedLoggers);
+
+        return osgiConfigurator;
+    }
+
+    public static LogManager mockLogManager(String level) {
+
+        LogManager logManager = mock(LogManager.class);
+
+        LogManager.Config config = new LogManager.Config() {
+
+            @Override
+            public Class<? extends Annotation> annotationType() {
+                return null;
+            }
+
+            @Override
+            public String org_apache_sling_commons_log_level() {
+                return level;
+            }
+
+            @Override
+            public int org_apache_sling_commons_log_maxCallerDataDepth() {
+                return 7;
+            }
+
+            @Override
+            public boolean org_apache_sling_commons_log_packagingDataEnabled() {
+                return true;
+            }
+        };
+
+        when(logManager.getConfig()).thenReturn(config);
+        return logManager;
+    }
+
+    public static Logger getLogger(String name) {
+        return (Logger) LoggerFactory.getLogger(name);
+    }
+
+    public static LoggerConfiguration mockLoggerConfiguration(String file, String level, String[] names) {
+
+        LoggerConfiguration loggerConfiguration = mock(LoggerConfiguration.class);
+        when(loggerConfiguration.getPid()).thenReturn(LoggerConfiguration.class.getName() + "~" + file);
+
+        LoggerConfiguration.Config config = createLoggerConfigurationConfig(file, level, names);
+        when(loggerConfiguration.getConfig()).thenReturn(config);
+
+        return loggerConfiguration;
+    }
+
+    public static LoggerConfiguration.Config createLoggerConfigurationConfig(String file, String level,
+            String[] names) {
+        return new LoggerConfiguration.Config() {
+
+            @Override
+            public Class<? extends Annotation> annotationType() {
+                return null;
+            }
+
+            @Override
+            public String org_apache_sling_commons_log_file() {
+                return file;
+            }
+
+            @Override
+            public String org_apache_sling_commons_log_level() {
+                return level;
+            }
+
+            @Override
+            public String[] org_apache_sling_commons_log_names() {
+                return names;
+            }
+
+        };
+    }
+}
diff --git a/json-logs/org.apache.sling.commons.log.json/src/test/resources/logback.xml b/json-logs/org.apache.sling.commons.log.json/src/test/resources/logback.xml
new file mode 100644
index 0000000..c94124f
--- /dev/null
+++ b/json-logs/org.apache.sling.commons.log.json/src/test/resources/logback.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<configuration>
+    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder"></encoder>
+    </appender>
+
+    <logger name="ROOT">
+        <appender-ref ref="console" />
+    </logger>
+</configuration>
\ No newline at end of file
diff --git a/json-logs/org.apache.sling.commons.logback.configurator/bnd.bnd b/json-logs/org.apache.sling.commons.logback.configurator/bnd.bnd
new file mode 100644
index 0000000..0267af0
--- /dev/null
+++ b/json-logs/org.apache.sling.commons.logback.configurator/bnd.bnd
@@ -0,0 +1,2 @@
+Fragment-Host: ch.qos.logback.classic
+Bundle-ClassPath: .
\ No newline at end of file
diff --git a/json-logs/org.apache.sling.commons.logback.configurator/pom.xml b/json-logs/org.apache.sling.commons.logback.configurator/pom.xml
new file mode 100644
index 0000000..594d132
--- /dev/null
+++ b/json-logs/org.apache.sling.commons.logback.configurator/pom.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling-bundle-parent</artifactId>
+        <version>46</version>
+        <relativePath />
+    </parent>
+
+    <artifactId>org.apache.sling.commons.log.logback.configurator</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+    <name>Apache Sling - Commons Logback Configurator</name>
+    <description>A fragment to configure logback when the repository starts</description>
+
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.build.outputTimestamp>10</project.build.outputTimestamp>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>biz.aQute.bnd</groupId>
+                <artifactId>bnd-baseline-maven-plugin</artifactId>
+                <configuration>
+                    <failOnMissing>false</failOnMissing>
+                </configuration>
+            </plugin>
+            
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>src/main/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <version>1.2.10</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>net.logstash.logback</groupId>
+            <artifactId>logstash-logback-encoder</artifactId>
+            <version>7.0.1</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.jetbrains</groupId>
+            <artifactId>annotations</artifactId>
+            <version>23.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.annotation.versioning</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+    </dependencies>
+</project>
\ No newline at end of file
diff --git a/json-logs/org.apache.sling.commons.logback.configurator/src/main/java/org/apache/sling/commons/log/compat/StartupConfigurator.java b/json-logs/org.apache.sling.commons.logback.configurator/src/main/java/org/apache/sling/commons/log/compat/StartupConfigurator.java
new file mode 100644
index 0000000..17ae425
--- /dev/null
+++ b/json-logs/org.apache.sling.commons.logback.configurator/src/main/java/org/apache/sling/commons/log/compat/StartupConfigurator.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.commons.log.compat;
+
+import java.util.Optional;
+
+import org.jetbrains.annotations.NotNull;
+
+import ch.qos.logback.classic.BasicConfigurator;
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.ConsoleAppender;
+import ch.qos.logback.core.encoder.Encoder;
+import net.logstash.logback.encoder.LogstashEncoder;
+
+public class StartupConfigurator extends BasicConfigurator {
+
+    public static final String APPENDER_NAME = "console";
+
+    @Override
+    public void configure(LoggerContext context) {
+        addInfo("Setting up default configuration.");
+
+        ConsoleAppender<ILoggingEvent> ca = new ConsoleAppender<>();
+        ca.setContext(context);
+        ca.setName(APPENDER_NAME);
+        ca.setEncoder(getEncoder(context));
+        ca.start();
+
+        Logger rootLogger = context.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
+        rootLogger.setLevel(Level.INFO);
+        rootLogger.addAppender(ca);
+
+        addInfo("Default configuration complete.");
+    }
+
+    @NotNull
+    private Encoder<ILoggingEvent> getEncoder(@NotNull LoggerContext context) {
+        LogstashEncoder logstashEncoder = new LogstashEncoder();
+        logstashEncoder.setContext(context);
+        logstashEncoder.setCustomFields(APPENDER_NAME);
+        logstashEncoder.start();
+
+        return logstashEncoder;
+    }
+
+}
diff --git a/json-logs/org.apache.sling.commons.logback.configurator/src/main/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator b/json-logs/org.apache.sling.commons.logback.configurator/src/main/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator
new file mode 100644
index 0000000..e3a055a
--- /dev/null
+++ b/json-logs/org.apache.sling.commons.logback.configurator/src/main/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator
@@ -0,0 +1 @@
+org.apache.sling.commons.log.compat.StartupConfigurator
\ No newline at end of file