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