[KARAF-2487] Add centralized log feature
diff --git a/assembly/src/main/resources/features.xml b/assembly/src/main/resources/features.xml
index f4889d5..24634e1 100644
--- a/assembly/src/main/resources/features.xml
+++ b/assembly/src/main/resources/features.xml
@@ -127,5 +127,11 @@
<bundle>mvn:org.apache.karaf.cellar.http/org.apache.karaf.cellar.http.balancer/${project.version}</bundle>
</feature>
+ <feature name="cellar-log" description="Cellar central log support" version="${project.version}">
+ <feature>log</feature>
+ <feature>cellar-hazelcast</feature>
+ <bundle>mvn:org.apache.karaf.cellar/org.apache.karaf.cellar.log/${project.version}</bundle>
+ </feature>
+
</features>
diff --git a/assembly/src/main/resources/hazelcast.xml b/assembly/src/main/resources/hazelcast.xml
index 43634e1..eb23a04 100644
--- a/assembly/src/main/resources/hazelcast.xml
+++ b/assembly/src/main/resources/hazelcast.xml
@@ -160,6 +160,14 @@
-->
<hz:merge-policy>com.hazelcast.map.merge.PassThroughMergePolicy</hz:merge-policy>
</hz:map>
+ <hz:map name="org.apache.karaf.cellar.log">
+ <hz:time-to-live-seconds>0</hz:time-to-live-seconds>
+ <hz:max-idle-seconds>0</hz:max-idle-seconds>
+ <hz:eviction-policy>LRU</hz:eviction-policy>
+ <hz:max-size policy="PER_PARTITION">5000</hz:max-size>
+ <hz:eviction-percentage>25</hz:eviction-percentage>
+ <hz:backup-count>0</hz:backup-count>
+ </hz:map>
<hz:multimap name="default">
<hz:backup-count>1</hz:backup-count>
diff --git a/log/NOTICE.txt b/log/NOTICE.txt
new file mode 100644
index 0000000..64cb235
--- /dev/null
+++ b/log/NOTICE.txt
@@ -0,0 +1,39 @@
+Apache Karaf Cellar
+Copyright 2011-2015 The Apache Software Foundation
+
+I. Used Software
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+Licensed under the Apache License 2.0.
+
+This product uses software developed at
+The OSGi Alliance (http://www.osgi.org/).
+Licensed under the Apache License 2.0.
+
+This product uses software developed at
+Hazelcast (http://www.hazelcast.com/).
+Licensed under the Apache License 2.0.
+
+This product uses software developed at
+OPS4J (http://www.ops4j.org/).
+Licensed under the Apache License 2.0.
+
+This product uses software developed at
+FUSE Source (http://www.fusesource.org/).
+Licensed under the Apache License 2.0.
+
+This product uses software developed at
+JClouds (http://www.jclouds.org/).
+Licensed under the Apache License 2.0.
+
+This product uses software developed at
+SLF4J (http://www.slf4j.org/).
+Licensed under the MIT License.
+
+This product includes software from http://www.json.org.
+Copyright (c) 2002 JSON.org
+
+II. License Summary
+- Apache License 2.0
+- MIT License
diff --git a/log/pom.xml b/log/pom.xml
new file mode 100644
index 0000000..1675f79
--- /dev/null
+++ b/log/pom.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/xsd/maven-4.0.0.xsd">
+
+ <!--
+
+ 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.
+ -->
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.karaf</groupId>
+ <artifactId>cellar</artifactId>
+ <version>4.0.1-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <groupId>org.apache.karaf.cellar</groupId>
+ <artifactId>org.apache.karaf.cellar.log</artifactId>
+ <packaging>bundle</packaging>
+ <name>Apache Karaf :: Cellar :: Log</name>
+
+ <dependencies>
+ <!-- Core dependencies -->
+ <dependency>
+ <groupId>org.apache.karaf.cellar</groupId>
+ <artifactId>org.apache.karaf.cellar.core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.logging</groupId>
+ <artifactId>pax-logging-service</artifactId>
+ <version>1.7.3</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.karaf.log</groupId>
+ <artifactId>org.apache.karaf.log.core</artifactId>
+ <version>${karaf.version}</version>
+ </dependency>
+
+ <!-- Logging dependencies -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.karaf.tooling</groupId>
+ <artifactId>karaf-services-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Export-Package>
+ !org.apache.karaf.cellar.log.internal.osgi,
+ !org.apache.karaf.cellar.log.management.internal,
+ org.apache.karaf.cellar.log*
+ </Export-Package>
+ <Import-Package>
+ org.slf4j;version="[1.6,2)";resolution:=optional,
+ *
+ </Import-Package>
+ <Private-Package>
+ org.apache.karaf.cellar.log.internal.osgi,
+ org.apache.karaf.cellar.log.management.internal,
+ org.apache.karaf.util.tracker;-split-package:=merge-first
+ </Private-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
\ No newline at end of file
diff --git a/log/src/main/java/org/apache/karaf/cellar/log/ClusterLogKey.java b/log/src/main/java/org/apache/karaf/cellar/log/ClusterLogKey.java
new file mode 100644
index 0000000..095655e
--- /dev/null
+++ b/log/src/main/java/org/apache/karaf/cellar/log/ClusterLogKey.java
@@ -0,0 +1,51 @@
+/*
+ * 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.karaf.cellar.log;
+
+import java.io.Serializable;
+
+public class ClusterLogKey implements Serializable {
+
+ private String nodeId;
+ private long timeStamp;
+ private String id;
+
+ public String getNodeId() {
+ return nodeId;
+ }
+
+ public void setNodeId(String nodeId) {
+ this.nodeId = nodeId;
+ }
+
+ public long getTimeStamp() {
+ return timeStamp;
+ }
+
+ public void setTimeStamp(long timeStamp) {
+ this.timeStamp = timeStamp;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+}
diff --git a/log/src/main/java/org/apache/karaf/cellar/log/ClusterLogRecord.java b/log/src/main/java/org/apache/karaf/cellar/log/ClusterLogRecord.java
new file mode 100644
index 0000000..9cca581
--- /dev/null
+++ b/log/src/main/java/org/apache/karaf/cellar/log/ClusterLogRecord.java
@@ -0,0 +1,87 @@
+/*
+ * 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.karaf.cellar.log;
+
+import java.io.Serializable;
+
+public class ClusterLogRecord implements Serializable {
+
+ private String level;
+ private String loggerName;
+ private String message;
+ private String renderedMessage;
+ private String threadName;
+ private String FQNOfLoggerClass;
+ private String[] throwableStringRep;
+
+ public String getLevel() {
+ return level;
+ }
+
+ public void setLevel(String level) {
+ this.level = level;
+ }
+
+ public String getLoggerName() {
+ return loggerName;
+ }
+
+ public void setLoggerName(String loggerName) {
+ this.loggerName = loggerName;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public String getRenderedMessage() {
+ return renderedMessage;
+ }
+
+ public void setRenderedMessage(String renderedMessage) {
+ this.renderedMessage = renderedMessage;
+ }
+
+ public String getThreadName() {
+ return threadName;
+ }
+
+ public void setThreadName(String threadName) {
+ this.threadName = threadName;
+ }
+
+ public String getFQNOfLoggerClass() {
+ return FQNOfLoggerClass;
+ }
+
+ public void setFQNOfLoggerClass(String FQNOfLoggerClass) {
+ this.FQNOfLoggerClass = FQNOfLoggerClass;
+ }
+
+ public String[] getThrowableStringRep() {
+ return throwableStringRep;
+ }
+
+ public void setThrowableStringRep(String[] throwableStringRep) {
+ this.throwableStringRep = throwableStringRep;
+ }
+
+}
diff --git a/log/src/main/java/org/apache/karaf/cellar/log/GetLogCommand.java b/log/src/main/java/org/apache/karaf/cellar/log/GetLogCommand.java
new file mode 100644
index 0000000..5aea23a
--- /dev/null
+++ b/log/src/main/java/org/apache/karaf/cellar/log/GetLogCommand.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed 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.karaf.cellar.log;
+
+import org.apache.karaf.cellar.core.command.Command;
+
+public class GetLogCommand extends Command<GetLogResult> {
+
+ protected String logger;
+
+ public GetLogCommand(String id) {
+ super(id);
+ }
+
+ public GetLogCommand(String id, String logger) {
+ super(id);
+ this.logger = logger;
+ }
+
+ public String getLogger() {
+ return logger;
+ }
+
+ public void setLogger(String logger) {
+ this.logger = logger;
+ }
+
+}
diff --git a/log/src/main/java/org/apache/karaf/cellar/log/GetLogCommandHandler.java b/log/src/main/java/org/apache/karaf/cellar/log/GetLogCommandHandler.java
new file mode 100644
index 0000000..98dfa57
--- /dev/null
+++ b/log/src/main/java/org/apache/karaf/cellar/log/GetLogCommandHandler.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed 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.karaf.cellar.log;
+
+import org.apache.karaf.cellar.core.command.CommandHandler;
+import org.apache.karaf.cellar.core.control.BasicSwitch;
+import org.apache.karaf.cellar.core.control.Switch;
+import org.apache.karaf.log.core.LogService;
+
+import java.util.Map;
+
+public class GetLogCommandHandler extends CommandHandler<GetLogCommand, GetLogResult> {
+
+ public static final String SWITCH_ID = "org.apache.karaf.cellar.log.get.switch";
+ private final Switch commandSwitch = new BasicSwitch(SWITCH_ID);
+
+ private LogService logService;
+
+ @Override
+ public GetLogResult execute(GetLogCommand command) {
+ Map<String, String> levels = logService.getLevel(command.getLogger());
+ GetLogResult result = new GetLogResult(command.getId(), levels);
+ return result;
+ }
+
+ @Override
+ public Class<GetLogCommand> getType() {
+ return GetLogCommand.class;
+ }
+
+ @Override
+ public Switch getSwitch() {
+ return commandSwitch;
+ }
+
+ public LogService getLogService() {
+ return logService;
+ }
+
+ public void setLogService(LogService logService) {
+ this.logService = logService;
+ }
+
+}
diff --git a/log/src/main/java/org/apache/karaf/cellar/log/GetLogResult.java b/log/src/main/java/org/apache/karaf/cellar/log/GetLogResult.java
new file mode 100644
index 0000000..96e570e
--- /dev/null
+++ b/log/src/main/java/org/apache/karaf/cellar/log/GetLogResult.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed 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.karaf.cellar.log;
+
+import org.apache.karaf.cellar.core.command.Result;
+
+import java.util.Map;
+
+public class GetLogResult extends Result {
+
+ protected Map<String, String> loggers;
+
+ public GetLogResult(String id) {
+ super(id);
+ }
+
+ public GetLogResult(String id, Map<String, String> loggers) {
+ super(id);
+ this.loggers = loggers;
+ }
+
+ public Map<String, String> getLoggers() {
+ return loggers;
+ }
+
+ public void setLoggers(Map<String, String> loggers) {
+ this.loggers = loggers;
+ }
+
+}
diff --git a/log/src/main/java/org/apache/karaf/cellar/log/GetLogResultHandler.java b/log/src/main/java/org/apache/karaf/cellar/log/GetLogResultHandler.java
new file mode 100644
index 0000000..9c73f96
--- /dev/null
+++ b/log/src/main/java/org/apache/karaf/cellar/log/GetLogResultHandler.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed 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.karaf.cellar.log;
+
+import org.apache.karaf.cellar.core.command.ResultHandler;
+
+public class GetLogResultHandler extends ResultHandler<GetLogResult> {
+
+ @Override
+ public Class<GetLogResult> getType() {
+ return GetLogResult.class;
+ }
+
+}
diff --git a/log/src/main/java/org/apache/karaf/cellar/log/LogAppender.java b/log/src/main/java/org/apache/karaf/cellar/log/LogAppender.java
new file mode 100644
index 0000000..8c59769
--- /dev/null
+++ b/log/src/main/java/org/apache/karaf/cellar/log/LogAppender.java
@@ -0,0 +1,66 @@
+/*
+ * 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.karaf.cellar.log;
+
+import org.apache.karaf.cellar.core.ClusterManager;
+import org.apache.karaf.cellar.core.Node;
+import org.ops4j.pax.logging.spi.PaxAppender;
+import org.ops4j.pax.logging.spi.PaxLoggingEvent;
+
+import java.util.Map;
+
+public class LogAppender implements PaxAppender {
+
+ public static final String LOG_MAP = "org.apache.karaf.cellar.log";
+
+ private ClusterManager clusterManager;
+
+ @Override
+ public void doAppend(PaxLoggingEvent event) {
+
+ Map<ClusterLogKey, ClusterLogRecord> clusterLog = clusterManager.getMap(LOG_MAP);
+
+ ClusterLogRecord record = new ClusterLogRecord();
+ ClusterLogKey key = new ClusterLogKey();
+
+ Node node = clusterManager.getNode();
+ String nodeId = node.getId();
+
+ key.setNodeId(nodeId);
+ key.setTimeStamp(event.getTimeStamp());
+ key.setId(clusterManager.generateId());
+
+ record.setFQNOfLoggerClass(event.getFQNOfLoggerClass());
+ record.setLevel(event.getLevel().toString());
+ record.setLoggerName(event.getLoggerName());
+ record.setMessage(event.getMessage());
+ record.setRenderedMessage(event.getRenderedMessage());
+ record.setThreadName(event.getThreadName());
+ record.setThrowableStringRep(event.getThrowableStrRep());
+
+ clusterLog.put(key, record);
+ }
+
+ public ClusterManager getClusterManager() {
+ return clusterManager;
+ }
+
+ public void setClusterManager(ClusterManager clusterManager) {
+ this.clusterManager = clusterManager;
+ }
+
+}
diff --git a/log/src/main/java/org/apache/karaf/cellar/log/SetLogCommand.java b/log/src/main/java/org/apache/karaf/cellar/log/SetLogCommand.java
new file mode 100644
index 0000000..6b171b2
--- /dev/null
+++ b/log/src/main/java/org/apache/karaf/cellar/log/SetLogCommand.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed 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.karaf.cellar.log;
+
+import org.apache.karaf.cellar.core.command.Command;
+
+public class SetLogCommand extends Command<SetLogResult> {
+
+ protected String logger;
+ protected String level;
+
+ public SetLogCommand(String id) {
+ super(id);
+ }
+
+ public SetLogCommand(String id, String logger, String level) {
+ super(id);
+ this.logger = logger;
+ this.level = level;
+ }
+
+ public String getLogger() {
+ return logger;
+ }
+
+ public void setLogger(String logger) {
+ this.logger = logger;
+ }
+
+ public String getLevel() {
+ return level;
+ }
+
+ public void setLevel(String level) {
+ this.level = level;
+ }
+
+}
diff --git a/log/src/main/java/org/apache/karaf/cellar/log/SetLogCommandHandler.java b/log/src/main/java/org/apache/karaf/cellar/log/SetLogCommandHandler.java
new file mode 100644
index 0000000..6a97ceb
--- /dev/null
+++ b/log/src/main/java/org/apache/karaf/cellar/log/SetLogCommandHandler.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed 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.karaf.cellar.log;
+
+import org.apache.karaf.cellar.core.command.CommandHandler;
+import org.apache.karaf.cellar.core.control.BasicSwitch;
+import org.apache.karaf.cellar.core.control.Switch;
+import org.apache.karaf.log.core.LogService;
+
+import java.util.Map;
+
+public class SetLogCommandHandler extends CommandHandler<SetLogCommand, SetLogResult> {
+
+ public static final String SWITCH_ID = "org.apache.karaf.cellar.log.set.switch";
+ private final Switch commandSwitch = new BasicSwitch(SWITCH_ID);
+
+ private LogService logService;
+
+ @Override
+ public SetLogResult execute(SetLogCommand command) {
+ logService.setLevel(command.getLogger(), command.getLevel());
+ SetLogResult result = new SetLogResult(command.getId());
+ return result;
+ }
+
+ @Override
+ public Class<SetLogCommand> getType() {
+ return SetLogCommand.class;
+ }
+
+ @Override
+ public Switch getSwitch() {
+ return commandSwitch;
+ }
+
+ public LogService getLogService() {
+ return logService;
+ }
+
+ public void setLogService(LogService logService) {
+ this.logService = logService;
+ }
+
+}
diff --git a/log/src/main/java/org/apache/karaf/cellar/log/SetLogResult.java b/log/src/main/java/org/apache/karaf/cellar/log/SetLogResult.java
new file mode 100644
index 0000000..5b49cae
--- /dev/null
+++ b/log/src/main/java/org/apache/karaf/cellar/log/SetLogResult.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed 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.karaf.cellar.log;
+
+import org.apache.karaf.cellar.core.command.Result;
+
+public class SetLogResult extends Result {
+
+ public SetLogResult(String id) {
+ super(id);
+ }
+
+}
diff --git a/log/src/main/java/org/apache/karaf/cellar/log/SetLogResultHandler.java b/log/src/main/java/org/apache/karaf/cellar/log/SetLogResultHandler.java
new file mode 100644
index 0000000..ebef243
--- /dev/null
+++ b/log/src/main/java/org/apache/karaf/cellar/log/SetLogResultHandler.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed 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.karaf.cellar.log;
+
+import org.apache.karaf.cellar.core.command.ResultHandler;
+
+public class SetLogResultHandler extends ResultHandler<SetLogResult> {
+
+ @Override
+ public Class<SetLogResult> getType() {
+ return SetLogResult.class;
+ }
+
+}
diff --git a/log/src/main/java/org/apache/karaf/cellar/log/internal/osgi/Activator.java b/log/src/main/java/org/apache/karaf/cellar/log/internal/osgi/Activator.java
new file mode 100644
index 0000000..95a0d6f
--- /dev/null
+++ b/log/src/main/java/org/apache/karaf/cellar/log/internal/osgi/Activator.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed 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.karaf.cellar.log.internal.osgi;
+
+import org.apache.karaf.cellar.core.ClusterManager;
+import org.apache.karaf.cellar.core.Producer;
+import org.apache.karaf.cellar.core.command.CommandStore;
+import org.apache.karaf.cellar.core.command.ExecutionContext;
+import org.apache.karaf.cellar.core.event.EventHandler;
+import org.apache.karaf.cellar.core.event.EventProducer;
+import org.apache.karaf.cellar.log.*;
+import org.apache.karaf.cellar.log.management.internal.CellarLogMBeanImpl;
+import org.apache.karaf.log.core.LogService;
+import org.apache.karaf.util.tracker.BaseActivator;
+import org.apache.karaf.util.tracker.annotation.ProvideService;
+import org.apache.karaf.util.tracker.annotation.RequireService;
+import org.apache.karaf.util.tracker.annotation.Services;
+import org.ops4j.pax.logging.spi.PaxAppender;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Hashtable;
+
+@Services(
+ provides = {
+ @ProvideService(PaxAppender.class),
+ @ProvideService(EventHandler.class)
+ },
+ requires = {
+ @RequireService(ClusterManager.class),
+ @RequireService(EventProducer.class),
+ @RequireService(CommandStore.class),
+ @RequireService(ExecutionContext.class),
+ @RequireService(ConfigurationAdmin.class),
+ @RequireService(LogService.class)
+ }
+)
+public class Activator extends BaseActivator {
+
+ private final static Logger LOGGER = LoggerFactory.getLogger(Activator.class);
+
+ private ServiceRegistration mbeanRegistration;
+
+ @Override
+ public void doStart() throws Exception {
+ LOGGER.debug("CELLAR LOG: retrieve cluster manager service");
+ ClusterManager clusterManager = getTrackedService(ClusterManager.class);
+ if (clusterManager == null) {
+ return;
+ }
+
+ LOGGER.debug("CELLAR LOG: retrieve producer service");
+ EventProducer producer = getTrackedService(EventProducer.class);
+ if (producer == null) {
+ return;
+ }
+
+ LOGGER.debug("CELLAR LOG: retrieve command store service");
+ CommandStore commandStore = getTrackedService(CommandStore.class);
+ if (commandStore == null) {
+ return;
+ }
+
+ LOGGER.debug("CELLAR LOG: retrieve execution context service");
+ ExecutionContext executionContext = getTrackedService(ExecutionContext.class);
+ if (executionContext == null) {
+ return;
+ }
+
+ LOGGER.debug("CELLAR LOG: retrieve configuration admin service");
+ ConfigurationAdmin configurationAdmin = getTrackedService(ConfigurationAdmin.class);
+ if (configurationAdmin == null) {
+ return;
+ }
+
+ LOGGER.debug("CELLAR LOG: retrieve log service");
+ LogService logService = getTrackedService(LogService.class);
+ if (logService == null) {
+ return;
+ }
+
+ LOGGER.debug("CELLAR LOG: init PaxAppender");
+ LogAppender paxAppender = new LogAppender();
+ paxAppender.setClusterManager(clusterManager);
+ Hashtable props = new Hashtable();
+ props.put("org.ops4j.pax.logging.appender.name", "CellarLogAppender");
+ register(PaxAppender.class, paxAppender, props);
+
+ LOGGER.debug("CELLAR LOG: register get log command handler");
+ GetLogCommandHandler getLogCommandHandler = new GetLogCommandHandler();
+ getLogCommandHandler.setProducer(producer);
+ getLogCommandHandler.setConfigurationAdmin(configurationAdmin);
+ getLogCommandHandler.setLogService(logService);
+ register(EventHandler.class, getLogCommandHandler);
+
+ LOGGER.debug("CELLAR LOG: register get log result handler");
+ GetLogResultHandler getLogResultHandler = new GetLogResultHandler();
+ getLogResultHandler.setCommandStore(commandStore);
+ register(EventHandler.class, getLogResultHandler);
+
+ LOGGER.debug("CELLAR LOG: register set log command handler");
+ SetLogCommandHandler setLogCommandHandler = new SetLogCommandHandler();
+ setLogCommandHandler.setProducer(producer);
+ setLogCommandHandler.setConfigurationAdmin(configurationAdmin);
+ setLogCommandHandler.setLogService(logService);
+ register(EventHandler.class, setLogCommandHandler);
+
+ LOGGER.debug("CELLAR LOG: register set log result handler");
+ SetLogResultHandler setLogResultHandler = new SetLogResultHandler();
+ setLogResultHandler.setCommandStore(commandStore);
+ register(EventHandler.class, setLogResultHandler);
+
+ LOGGER.debug("CELLAR LOG: registre MBean");
+ CellarLogMBeanImpl mbean = new CellarLogMBeanImpl();
+ mbean.setClusterManager(clusterManager);
+ mbean.setExecutionContext(executionContext);
+ props = new Hashtable();
+ props.put("jmx.objectname", "org.apache.karaf.cellar:type=log,name=" + System.getProperty("karaf.name"));
+ mbeanRegistration = bundleContext.registerService(getInterfaceNames(mbean), mbean, props);
+ }
+
+ @Override
+ public void doStop() {
+ super.doStop();
+
+ if (mbeanRegistration != null) {
+ mbeanRegistration.unregister();
+ mbeanRegistration = null;
+ }
+ }
+
+}
diff --git a/log/src/main/java/org/apache/karaf/cellar/log/management/CellarLogMBean.java b/log/src/main/java/org/apache/karaf/cellar/log/management/CellarLogMBean.java
new file mode 100644
index 0000000..8d6fcf2
--- /dev/null
+++ b/log/src/main/java/org/apache/karaf/cellar/log/management/CellarLogMBean.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed 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.karaf.cellar.log.management;
+
+import javax.management.openmbean.TabularData;
+import java.util.List;
+
+/**
+ * Describe the operations and attributes on the CellarLogMBean.
+ */
+public interface CellarLogMBean {
+
+ /**
+ * Display the cluster log messages.
+ *
+ * @param logger The logger name to filter or null.
+ * @param nodeId The node ID or alias to filter or null for all nodes.
+ * @param entries The number of entries to display limit (0 means no limit).
+ * @return The list of log messages.
+ */
+ List<String> displayLog(String logger, String nodeId, int entries);
+
+ /**
+ * Add a log message on the cluster.
+ *
+ * @param message The message to log.
+ * @param level The log message level (default is INFO when null).
+ * @param nodeId The node ID or alias or null for all nodes.
+ */
+ void logMessage(String message, String level, String nodeId);
+
+ /**
+ * Set the log level on the cluster.
+ *
+ * @param logger The logger to set the level or null for the root logger.
+ * @param level The level to set.
+ * @param nodeId The node ID or alias or null for all nodes.
+ */
+ void setLevel(String level, String logger, String nodeId) throws Exception;
+
+ /**
+ * Get the log level on the cluster.
+ *
+ * @param logger The logger to filter or null for all loggers.
+ * @param nodeId The node ID or alias or null for all nodes.
+ * @return A tabular data reprepsenting the levels.
+ */
+ TabularData getLevel(String logger, String nodeId) throws Exception;
+
+}
diff --git a/log/src/main/java/org/apache/karaf/cellar/log/management/internal/CellarLogMBeanImpl.java b/log/src/main/java/org/apache/karaf/cellar/log/management/internal/CellarLogMBeanImpl.java
new file mode 100644
index 0000000..c5c2e2a
--- /dev/null
+++ b/log/src/main/java/org/apache/karaf/cellar/log/management/internal/CellarLogMBeanImpl.java
@@ -0,0 +1,208 @@
+/*
+ * Licensed 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.karaf.cellar.log.management.internal;
+
+import org.apache.karaf.cellar.core.ClusterManager;
+import org.apache.karaf.cellar.core.Node;
+import org.apache.karaf.cellar.core.command.ExecutionContext;
+import org.apache.karaf.cellar.log.*;
+import org.apache.karaf.cellar.log.management.CellarLogMBean;
+
+import javax.management.NotCompliantMBeanException;
+import javax.management.StandardMBean;
+import javax.management.openmbean.*;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+public class CellarLogMBeanImpl extends StandardMBean implements CellarLogMBean {
+
+ private ClusterManager clusterManager;
+ private ExecutionContext executionContext;
+
+ public CellarLogMBeanImpl() throws NotCompliantMBeanException {
+ super(CellarLogMBean.class);
+ }
+
+ public ClusterManager getClusterManager() {
+ return clusterManager;
+ }
+
+ public void setClusterManager(ClusterManager clusterManager) {
+ this.clusterManager = clusterManager;
+ }
+
+ public ExecutionContext getExecutionContext() {
+ return executionContext;
+ }
+
+ public void setExecutionContext(ExecutionContext executionContext) {
+ this.executionContext = executionContext;
+ }
+
+ @Override
+ public List<String> displayLog(String logger, String nodeId, int entries) {
+ List<String> result = new ArrayList<String>();
+
+ String node = null;
+ if (nodeId != null && clusterManager.findNodeByIdOrAlias(nodeId) == null) {
+ throw new IllegalArgumentException("Node " + nodeId + " doesn't exist");
+ }
+ if (nodeId != null && clusterManager.findNodeByIdOrAlias(nodeId) != null) {
+ node = clusterManager.findNodeByIdOrAlias(nodeId).getId();
+ }
+ ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+ try {
+ Map<ClusterLogKey, ClusterLogRecord> clusterLog = clusterManager.getMap(LogAppender.LOG_MAP);
+ int index = 0;
+ for (ClusterLogKey key : clusterLog.keySet()) {
+ if (entries == 0 || (entries != 0 && index < entries)) {
+ ClusterLogRecord record = clusterLog.get(key);
+ if (node == null || (node != null && key.getNodeId().equals(node))) {
+ if (logger == null || (logger != null && logger.equals("ALL")) || (logger != null && record.getLoggerName() != null && record.getLoggerName().contains(logger))) {
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
+ String message = key.getNodeId() + " | "
+ + dateFormat.format(new Date(key.getTimeStamp())) + " | "
+ + record.getLevel() + " | "
+ + record.getThreadName() + " | "
+ + record.getLoggerName() + " | "
+ + record.getMessage();
+ result.add(message);
+ index++;
+ }
+ }
+ }
+ }
+ } finally {
+ Thread.currentThread().setContextClassLoader(originalClassLoader);
+ }
+
+ return result;
+ }
+
+ @Override
+ public void logMessage(String message, String nodeId, String level) {
+ if (message == null) {
+ throw new IllegalArgumentException("Message is required");
+ }
+ if (level != null && !level.equalsIgnoreCase("INFO") && !level.equalsIgnoreCase("DEBUG")
+ && !level.equalsIgnoreCase("WARN") && !level.equalsIgnoreCase("ERROR")) {
+ throw new IllegalArgumentException("Incorrect level value");
+ }
+ if (level == null) {
+ level = "INFO";
+ }
+ if (nodeId != null && clusterManager.findNodeByIdOrAlias(nodeId) == null) {
+ throw new IllegalArgumentException("Node " + nodeId + " doesn't exist");
+ }
+ long timestamp = System.currentTimeMillis();
+ String id = clusterManager.generateId();
+
+ ClusterLogKey key = new ClusterLogKey();
+ key.setNodeId(nodeId);
+ key.setTimeStamp(timestamp);
+ key.setId(id);
+
+ ClusterLogRecord record = new ClusterLogRecord();
+ record.setMessage(message);
+ record.setLevel(level);
+
+ ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+ try {
+ Map<ClusterLogKey, ClusterLogRecord> clusterLog = clusterManager.getMap(LogAppender.LOG_MAP);
+ clusterLog.put(key, record);
+ } finally {
+ Thread.currentThread().setContextClassLoader(originalClassLoader);
+ }
+ }
+
+ @Override
+ public void setLevel(String level, String logger, String nodeId) throws Exception {
+ SetLogCommand command = new SetLogCommand(clusterManager.generateId());
+ command.setTimeout(30 * 1000);
+
+ Set<Node> recipientList = new HashSet<Node>();
+ if (nodeId != null && clusterManager.findNodeByIdOrAlias(nodeId) == null) {
+ throw new IllegalArgumentException("Node " + nodeId + " doesn't exist");
+ }
+ if (nodeId == null) {
+ recipientList = clusterManager.listNodes();
+ } else {
+ recipientList.add(clusterManager.findNodeByIdOrAlias(nodeId));
+ }
+
+ if (recipientList.size() < 1)
+ throw new IllegalArgumentException("No recipient list");
+
+ command.setDestination(recipientList);
+ command.setLogger(logger);
+ command.setLevel(level);
+
+ Map<Node, SetLogResult> results = executionContext.execute(command);
+ if (results == null || results.isEmpty()) {
+ throw new IllegalStateException("No result received within given timeout");
+ }
+ }
+
+ @Override
+ public TabularData getLevel(String logger, String nodeId) throws Exception {
+ if (nodeId != null && clusterManager.findNodeByIdOrAlias(nodeId) == null) {
+ throw new IllegalArgumentException("Node " + nodeId + " doesn't exist");
+ }
+
+ Set<Node> recipientList = new HashSet<Node>();
+ if (nodeId == null) {
+ recipientList = clusterManager.listNodes();
+ } else {
+ recipientList.add(clusterManager.findNodeByIdOrAlias(nodeId));
+ }
+
+ CompositeType levelType = new CompositeType("Level", "Log Levels",
+ new String[]{ "node", "logger", "level" },
+ new String[]{ "Node ID", "Logger name", "Log level"},
+ new OpenType[]{ SimpleType.STRING, SimpleType.STRING, SimpleType.STRING });
+
+ TabularType tabularType = new TabularType("Levels", "Table of all log levels",
+ levelType, new String[]{ "node", "logger" });
+ TabularData tabularData = new TabularDataSupport(tabularType);
+
+ if (recipientList.size() < 1)
+ return null;
+
+ GetLogCommand command = new GetLogCommand(clusterManager.generateId());
+ command.setTimeout(30 * 1000);
+ command.setDestination(recipientList);
+ command.setLogger(logger);
+
+ Map<Node, GetLogResult> results = executionContext.execute(command);
+ if (results == null || results.isEmpty()) {
+ throw new IllegalStateException("No result received within given timeout");
+ } else {
+ for (Node node : results.keySet()) {
+ GetLogResult result = results.get(node);
+ Map<String, String> loggers = result.getLoggers();
+ for (String l : loggers.keySet()) {
+ CompositeData data = new CompositeDataSupport(levelType,
+ new String[]{ "node", "logger", "level" },
+ new Object[]{ node.getId(), l, loggers.get(l) });
+ tabularData.put(data);
+ }
+ }
+ }
+
+ return tabularData;
+ }
+
+}
diff --git a/log/src/main/java/org/apache/karaf/cellar/log/shell/GetLog.java b/log/src/main/java/org/apache/karaf/cellar/log/shell/GetLog.java
new file mode 100644
index 0000000..3b9129a
--- /dev/null
+++ b/log/src/main/java/org/apache/karaf/cellar/log/shell/GetLog.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed 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.karaf.cellar.log.shell;
+
+import org.apache.karaf.cellar.core.Node;
+import org.apache.karaf.cellar.core.command.ExecutionContext;
+import org.apache.karaf.cellar.core.shell.CellarCommandSupport;
+import org.apache.karaf.cellar.core.shell.completer.AllNodeCompleter;
+import org.apache.karaf.cellar.log.GetLogCommand;
+import org.apache.karaf.cellar.log.GetLogResult;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.table.ShellTable;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@Command(scope = "cluster", name = "log-get", description = "Show the current set log loggers")
+@Service
+public class GetLog extends CellarCommandSupport {
+
+ @Argument(index = 0, name = "logger", description = "The name of the logger, ALL or ROOT (default)", required = false, multiValued = false)
+ String logger;
+
+ @Argument(index = 1, name = "node", description = "The node(s) ID or alias", required = false, multiValued = true)
+ @Completion(AllNodeCompleter.class)
+ List<String> nodes;
+
+ @Option(name = "-t", aliases = { "--timeout" }, description = "Consumer command timeout (in seconds)", required = false, multiValued = false)
+ protected long timeout = 30;
+
+ @Reference
+ protected ExecutionContext executionContext;
+
+ public ExecutionContext getExecutionContext() {
+ return executionContext;
+ }
+
+ public void setExecutionContext(ExecutionContext executionContext) {
+ this.executionContext = executionContext;
+ }
+
+ @Override
+ public Object doExecute() throws Exception {
+ GetLogCommand command = new GetLogCommand(clusterManager.generateId());
+ command.setTimeout(timeout * 1000);
+
+ ShellTable table = new ShellTable();
+ table.column(" ");
+ table.column("Node");
+ table.column("Logger");
+ table.column("Level");
+
+ Set<Node> recipientList = new HashSet<Node>();
+ if (nodes != null && !nodes.isEmpty()) {
+ for (String nodeId : nodes) {
+ Node node = clusterManager.findNodeByIdOrAlias(nodeId);
+ if (node == null) {
+ System.err.println("Node " + nodeId + " doesn't exist");
+ } else {
+ recipientList.add(node);
+ }
+ }
+ } else {
+ recipientList = clusterManager.listNodes();
+ }
+
+ if (recipientList.size() < 1)
+ return null;
+
+ command.setDestination(recipientList);
+ command.setLogger(logger);
+
+ Map<Node, GetLogResult> results = executionContext.execute(command);
+ if (results == null || results.isEmpty()) {
+ System.err.println("No result received within given timeout");
+ } else {
+ for (Node node : results.keySet()) {
+ String local = "";
+ if (node.equals(clusterManager.getNode()))
+ local = "x";
+ GetLogResult result = results.get(node);
+ Map<String, String> loggers = result.getLoggers();
+ for (String logger : loggers.keySet()) {
+ table.addRow().addContent(local, node.getId(), logger, loggers.get(logger));
+ }
+ }
+ }
+
+ table.print(System.out);
+
+ return null;
+ }
+
+}
diff --git a/log/src/main/java/org/apache/karaf/cellar/log/shell/LogClear.java b/log/src/main/java/org/apache/karaf/cellar/log/shell/LogClear.java
new file mode 100644
index 0000000..0a746c1
--- /dev/null
+++ b/log/src/main/java/org/apache/karaf/cellar/log/shell/LogClear.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed 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.karaf.cellar.log.shell;
+
+import org.apache.karaf.cellar.core.Node;
+import org.apache.karaf.cellar.core.shell.CellarCommandSupport;
+import org.apache.karaf.cellar.core.shell.completer.AllNodeCompleter;
+import org.apache.karaf.cellar.log.ClusterLogKey;
+import org.apache.karaf.cellar.log.ClusterLogRecord;
+import org.apache.karaf.cellar.log.LogAppender;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+import java.util.Map;
+
+@Command(scope = "cluster", name = "log-clear", description = "Clean the cluster log messages")
+@Service
+public class LogClear extends CellarCommandSupport {
+
+ @Argument(index = 0, name = "nodeIdOrAlias", description = "The node ID or alias", required = false, multiValued = false)
+ @Completion(AllNodeCompleter.class)
+ String nodeIdOrAlias;
+
+ @Override
+ public Object doExecute() throws Exception {
+ ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+ try {
+ Map<ClusterLogKey, ClusterLogRecord> clusterLog = clusterManager.getMap(LogAppender.LOG_MAP);
+ if (nodeIdOrAlias == null) {
+ clusterLog.clear();
+ } else {
+ Node node = clusterManager.findNodeByIdOrAlias(nodeIdOrAlias);
+ if (node == null) {
+ System.err.println("Node " + nodeIdOrAlias + " doesn't exist");
+ return null;
+ }
+ for (ClusterLogKey key : clusterLog.keySet()) {
+ if (key.getNodeId().equals(node.getId())) {
+ clusterLog.remove(key);
+ }
+ }
+ }
+ } finally {
+ Thread.currentThread().setContextClassLoader(originalClassLoader);
+ }
+ return null;
+ }
+
+}
diff --git a/log/src/main/java/org/apache/karaf/cellar/log/shell/LogDisplay.java b/log/src/main/java/org/apache/karaf/cellar/log/shell/LogDisplay.java
new file mode 100644
index 0000000..a4bc432
--- /dev/null
+++ b/log/src/main/java/org/apache/karaf/cellar/log/shell/LogDisplay.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed 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.karaf.cellar.log.shell;
+
+import org.apache.karaf.cellar.core.shell.CellarCommandSupport;
+import org.apache.karaf.cellar.core.shell.completer.AllNodeCompleter;
+import org.apache.karaf.cellar.log.ClusterLogKey;
+import org.apache.karaf.cellar.log.ClusterLogRecord;
+import org.apache.karaf.cellar.log.LogAppender;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Map;
+
+@Command(scope = "cluster", name = "log-display", description = "Display the cluster log messages")
+@Service
+public class LogDisplay extends CellarCommandSupport {
+
+ @Argument(index = 0, name = "logger", description = "The logger name", required = false, multiValued = false)
+ String logger;
+
+ @Argument(index = 1, name = "node", description = "The node ID or alias", required = false, multiValued = false)
+ @Completion(AllNodeCompleter.class)
+ String nodeIdOrAlias;
+
+ @Option(name = "-n", description = "Number of entries to display", required = false, multiValued = false)
+ int entries;
+
+ @Override
+ public Object doExecute() throws Exception {
+ String nodeId = null;
+ if (nodeIdOrAlias != null && clusterManager.findNodeByIdOrAlias(nodeIdOrAlias) == null) {
+ System.err.println("Node " + nodeIdOrAlias + " doesn't exist");
+ return null;
+ }
+ if (nodeIdOrAlias != null && clusterManager.findNodeByIdOrAlias(nodeIdOrAlias) != null) {
+ nodeId = clusterManager.findNodeByIdOrAlias(nodeIdOrAlias).getId();
+ }
+ ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+ try {
+ Map<ClusterLogKey, ClusterLogRecord> clusterLog = clusterManager.getMap(LogAppender.LOG_MAP);
+ int index = 0;
+ for (ClusterLogKey key : clusterLog.keySet()) {
+ if (entries == 0 || (entries != 0 && index < entries)) {
+ ClusterLogRecord record = clusterLog.get(key);
+ if (nodeId == null || (nodeId != null && key.getNodeId().equals(nodeId))) {
+ if (logger == null || (logger != null && logger.equals("ALL")) || (logger != null && record.getLoggerName() != null && record.getLoggerName().contains(logger))) {
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
+ System.out.println(key.getNodeId() + " | "
+ + dateFormat.format(new Date(key.getTimeStamp())) + " | "
+ + record.getLevel() + " | "
+ + record.getThreadName() + " | "
+ + record.getLoggerName() + " | "
+ + record.getMessage());
+ index++;
+ }
+ }
+ }
+ }
+ } finally {
+ Thread.currentThread().setContextClassLoader(originalClassLoader);
+ }
+ return null;
+ }
+
+}
diff --git a/log/src/main/java/org/apache/karaf/cellar/log/shell/LogDisplayException.java b/log/src/main/java/org/apache/karaf/cellar/log/shell/LogDisplayException.java
new file mode 100644
index 0000000..9468c89
--- /dev/null
+++ b/log/src/main/java/org/apache/karaf/cellar/log/shell/LogDisplayException.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed 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.karaf.cellar.log.shell;
+
+import org.apache.karaf.cellar.core.Node;
+import org.apache.karaf.cellar.core.shell.CellarCommandSupport;
+import org.apache.karaf.cellar.core.shell.completer.AllNodeCompleter;
+import org.apache.karaf.cellar.log.ClusterLogKey;
+import org.apache.karaf.cellar.log.ClusterLogRecord;
+import org.apache.karaf.cellar.log.LogAppender;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+import java.util.Map;
+
+@Command(scope = "cluster", name = "log-exception-display", description = "Display the last occurred exception from the cluster log")
+@Service
+public class LogDisplayException extends CellarCommandSupport {
+
+ @Argument(index = 0, name = "logger", description = "The name of the logger. This can be ROOT, ALL, or specific logger", required = false, multiValued = false)
+ String logger;
+
+ @Argument(index = 1, name = "node", description = "The node ID or alias", required = false, multiValued = false)
+ @Completion(AllNodeCompleter.class)
+ String nodeIdOrAlias;
+
+ @Override
+ public Object doExecute() throws Exception {
+ String nodeId = null;
+ if (nodeIdOrAlias != null) {
+ Node node = clusterManager.findNodeByIdOrAlias(nodeIdOrAlias);
+ if (node == null) {
+ System.err.println("Node " + nodeIdOrAlias + " doesn't exist");
+ return null;
+ }
+ nodeId = node.getId();
+ }
+ ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+ try {
+ Map<ClusterLogKey, ClusterLogRecord> clusterLog = clusterManager.getMap(LogAppender.LOG_MAP);
+ for (ClusterLogKey key : clusterLog.keySet()) {
+ ClusterLogRecord record = clusterLog.get(key);
+ if (record.getThrowableStringRep() != null && record.getThrowableStringRep().length > 0) {
+ if (nodeId == null || (nodeId != null && key.getNodeId().equals(nodeId))) {
+ if (logger == null || (logger != null && record.getLoggerName().contains(logger))) {
+ for (String throwable : record.getThrowableStringRep()) {
+ System.out.println(throwable);
+ }
+ System.out.println();
+ }
+ }
+ }
+ }
+ } finally {
+ Thread.currentThread().setContextClassLoader(originalClassLoader);
+ }
+ return null;
+ }
+
+}
diff --git a/log/src/main/java/org/apache/karaf/cellar/log/shell/LogMessage.java b/log/src/main/java/org/apache/karaf/cellar/log/shell/LogMessage.java
new file mode 100644
index 0000000..d0cdafb
--- /dev/null
+++ b/log/src/main/java/org/apache/karaf/cellar/log/shell/LogMessage.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed 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.karaf.cellar.log.shell;
+
+import org.apache.karaf.cellar.core.shell.CellarCommandSupport;
+import org.apache.karaf.cellar.core.shell.completer.AllNodeCompleter;
+import org.apache.karaf.cellar.log.ClusterLogKey;
+import org.apache.karaf.cellar.log.ClusterLogRecord;
+import org.apache.karaf.cellar.log.LogAppender;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
+
+import java.util.Map;
+
+@Command(scope = "cluster", name = "log-log", description = "Add a message in the cluster log")
+@Service
+public class LogMessage extends CellarCommandSupport {
+
+ @Argument(index = 0, name = "message", description = "The message to log", required = true, multiValued = false)
+ String message;
+
+ @Argument(index = 1, name = "node", description = "The node ID or alias", required = false, multiValued = false)
+ @Completion(AllNodeCompleter.class)
+ String nodeId;
+
+ @Option(name = "--level", aliases = { "-l" }, description = "The level the message will be logged at", required = false, multiValued = false)
+ @Completion(value = StringsCompleter.class, values = { "DEBUG", "INFO", "WARN", "ERROR" })
+ String level = "INFO";
+
+ public Object doExecute() throws Exception {
+ if (nodeId != null && clusterManager.findNodeByIdOrAlias(nodeId) == null) {
+ System.err.println("Node " + nodeId + " doesn't exist");
+ return null;
+ }
+
+ if (nodeId == null) {
+ nodeId = clusterManager.getNode().getId();
+ }
+ long timestamp = System.currentTimeMillis();
+ String id = clusterManager.generateId();
+
+ ClusterLogKey key = new ClusterLogKey();
+ key.setNodeId(nodeId);
+ key.setTimeStamp(timestamp);
+ key.setId(id);
+
+ ClusterLogRecord record = new ClusterLogRecord();
+ record.setMessage(message);
+ record.setLevel(level);
+
+ ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+ try {
+ Map<ClusterLogKey, ClusterLogRecord> clusterLog = clusterManager.getMap(LogAppender.LOG_MAP);
+ clusterLog.put(key, record);
+ } finally {
+ Thread.currentThread().setContextClassLoader(originalClassLoader);
+ }
+
+ return null;
+ }
+
+}
diff --git a/log/src/main/java/org/apache/karaf/cellar/log/shell/SetLog.java b/log/src/main/java/org/apache/karaf/cellar/log/shell/SetLog.java
new file mode 100644
index 0000000..5a5eb70
--- /dev/null
+++ b/log/src/main/java/org/apache/karaf/cellar/log/shell/SetLog.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed 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.karaf.cellar.log.shell;
+
+import org.apache.karaf.cellar.core.Node;
+import org.apache.karaf.cellar.core.command.ExecutionContext;
+import org.apache.karaf.cellar.core.shell.CellarCommandSupport;
+import org.apache.karaf.cellar.core.shell.completer.AllNodeCompleter;
+import org.apache.karaf.cellar.log.SetLogCommand;
+import org.apache.karaf.cellar.log.SetLogResult;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@Command(scope = "cluster", name = "log-set", description = "Set the log level")
+@Service
+public class SetLog extends CellarCommandSupport {
+
+ @Argument(index = 0, name = "level", description = "The log level to set (TRACE, DEBUG, INFO, WARN, ERROR) or DEFAULT to unset", required = true, multiValued = false)
+ @Completion(value = StringsCompleter.class, values = { "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "DEFAULT" })
+ String level;
+
+ @Argument(index = 1, name = "logger", description = "Logger name or ROOT (default)", required = false, multiValued = false)
+ String logger;
+
+ @Argument(index = 2, name = "node", description = "The node(s) ID or alias", required = false, multiValued = true)
+ @Completion(AllNodeCompleter.class)
+ List<String> nodes;
+
+ @Option(name = "-t", aliases = { "--timeout" }, description = "Consumer command timeout (in seconds)", required = false, multiValued = false)
+ protected long timeout = 30;
+
+ @Reference
+ protected ExecutionContext executionContext;
+
+ public ExecutionContext getExecutionContext() {
+ return executionContext;
+ }
+
+ public void setExecutionContext(ExecutionContext executionContext) {
+ this.executionContext = executionContext;
+ }
+
+ @Override
+ public Object doExecute() throws Exception {
+ SetLogCommand command = new SetLogCommand(clusterManager.generateId());
+ command.setTimeout(timeout * 1000);
+
+ Set<Node> recipientList = new HashSet<Node>();
+ if (nodes != null && !nodes.isEmpty()) {
+ for (String nodeId : nodes) {
+ Node node = clusterManager.findNodeByIdOrAlias(nodeId);
+ if (node == null) {
+ System.err.println("Node " + nodeId + " doesn't exist");
+ } else {
+ recipientList.add(node);
+ }
+ }
+ } else {
+ recipientList = clusterManager.listNodes();
+ }
+
+ if (recipientList.size() < 1)
+ return null;
+
+ command.setDestination(recipientList);
+ command.setLogger(logger);
+ command.setLevel(level);
+
+ Map<Node, SetLogResult> results = executionContext.execute(command);
+ if (results == null || results.isEmpty()) {
+ System.err.println("No result received within given timeout");
+ }
+
+ return null;
+ }
+
+}
diff --git a/manual/src/main/asciidoc/index.adoc b/manual/src/main/asciidoc/index.adoc
index c8200ae..a098d79 100644
--- a/manual/src/main/asciidoc/index.adoc
+++ b/manual/src/main/asciidoc/index.adoc
@@ -40,6 +40,8 @@
include::user-guide/event.adoc[]
+include::user-guide/log.adoc[]
+
include::user-guide/http-balancer.adoc[]
include::user-guide/http-session.adoc[]
diff --git a/manual/src/main/asciidoc/user-guide/deploy.adoc b/manual/src/main/asciidoc/user-guide/deploy.adoc
index c98be09..a04b1e2 100644
--- a/manual/src/main/asciidoc/user-guide/deploy.adoc
+++ b/manual/src/main/asciidoc/user-guide/deploy.adoc
@@ -81,4 +81,16 @@
karaf@root()> feature:install cellar-cloud
----
+The cellar-http feature adds HTTP support:
+
+----
+karaf@root()> feature:install cellar-http
+----
+
+The cellar-log feature adds centralized log support:
+
+----
+karaf@root()> feature:install cellar-log
+----
+
Please, see the sections dedicated to these features for details.
diff --git a/manual/src/main/asciidoc/user-guide/log.adoc b/manual/src/main/asciidoc/user-guide/log.adoc
new file mode 100644
index 0000000..06ad183
--- /dev/null
+++ b/manual/src/main/asciidoc/user-guide/log.adoc
@@ -0,0 +1,122 @@
+//
+// Licensed 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.
+//
+
+=== Centralized Log
+
+Apache Karaf Cellar is able to centralize and manage log service on each node.
+
+==== Enable Cellar log
+
+Centralized log support is an optionnal feature that you can install using:
+
+----
+karaf@root()> feature:install cellar-log
+----
+
+==== Display log messages and exceptions
+
+The `cluster:log-display` command allows you to display all log messages on the cluster or on a specific node in the
+cluster.
+
+Without argument, `cluster:log-display` command will display log messages from all nodes in the cluster:
+
+----
+karaf@node1()> cluster:log-display
+172.17.42.1:5701 | 2016-07-09T22:21:34+0200 | INFO | pool-52-thread-1 | org.apache.karaf.shell.impl.action.osgi.CommandExtension | Registering commands for bundle org.apache.karaf.cellar.log/4.0.1.SNAPSHOT
+172.17.42.1:5701 | 2016-07-09T22:21:34+0200 | INFO | pool-52-thread-1 | org.apache.karaf.features.internal.service.FeaturesServiceImpl | Done.
+----
+
+You can also specify a node (ID or alias) to display only log messages for this specific node:
+
+----
+karaf@node1()> cluster:log-display ALL 172.17.42.1:5701
+172.17.42.1:5701 | 2016-07-09T22:21:34+0200 | INFO | pool-52-thread-1 | org.apache.karaf.shell.impl.action.osgi.CommandExtension | Registering commands for bundle org.apache.karaf.cellar.log/4.0.1.SNAPSHOT
+172.17.42.1:5701 | 2016-07-09T22:21:34+0200 | INFO | pool-52-thread-1 | org.apache.karaf.features.internal.service.FeaturesServiceImpl | Done.
+----
+
+`ALL` keyword means log messages for any logger.
+
+It's also possible to display the log message only for a specific logger:
+
+----
+karaf@node1()> cluster:log-display org.apache.karaf.features
+172.17.42.1:5701 | 2016-07-09T22:21:34+0200 | INFO | pool-52-thread-1 | org.apache.karaf.features.internal.service.FeaturesServiceImpl | Done.
+----
+
+The `cluster:log-exception-display` displays the last occurred exception.
+
+Like the `cluster:log-display` command, you can also filter by logger and node.
+
+==== Log a message
+
+At any time, you can log a message of your choice using `cluster:log-log` command, from the local node and any node
+in the cluster:
+
+----
+karaf@root()> cluster:log-log "My own message"
+karaf@root()> cluster:log-log "My own message" 172.17.42.1:5701
+----
+
+==== Clear log messages
+
+The `cluster:log-clear` command cleans the log messages.
+
+Without argument, the command cleans all log messages on the cluster:
+
+----
+karaf@root()> cluster:log-clear
+----
+
+You can specify the node (ID or alias) for which we remove only the log message:
+
+----
+karaf@root()> cluster:log-clear
+----
+
+===== Get and set log levels
+
+You can change the log level for any logger on all nodes in the cluster or on a specific node using the
+`cluster:log-set` command.
+
+----
+karaf@root()> cluster:log-set DEBUG
+----
+
+You can specify the logger to change:
+
+----
+karaf@root()> cluster:log-set DEBUG org.apache.karaf.cellar
+----
+
+And you can also change on a specific node in the cluster instead of all nodes:
+
+----
+karaf@root()> cluster:log-set DEBUG ROOT 172.17.42.1:5701
+----
+
+On the other hand, you can display the current log level on all nodes, for each logger using the `cluster:log-set`
+command:
+
+----
+karaf@node1()> cluster:log-get
+Node 172.17.42.1:5701 (x)
+Logger | Level
+--------------
+ROOT | INFO
+...
+----
+
+==== JMX MBeans
+
+All actions you can do using the shell commands can be done with the `CellarLogMBean`.
\ No newline at end of file
diff --git a/manual/src/main/asciidoc/user-guide/nodes.adoc b/manual/src/main/asciidoc/user-guide/nodes.adoc
index b0f6558..bda3b75 100644
--- a/manual/src/main/asciidoc/user-guide/nodes.adoc
+++ b/manual/src/main/asciidoc/user-guide/nodes.adoc
@@ -25,10 +25,10 @@
----
karaf@root()> cluster:node-list
- | Id | Host Name | Port
--------------------------------------
-x | node2:5702 | node2 | 5702
- | node1:5701 | node1 | 5701
+ | Id | Alias | Host Name | Port
+----------------------------------------------
+x | node2:5702 | | node2 | 5702
+ | node1:5701 | | node1 | 5701
----
The starting 'x' indicates that it's the Karaf instance on which you are logged on (the local node).
@@ -48,6 +48,9 @@
See link:hazelcast[Core Configuration section] for details.
====
+The `cluster:node-alias` command allows you to define an alias to a node. Any Cellar command using a node as argument
+or option can use either node ID or node alias.
+
==== Testing nodes
You can ping a node to test it:
diff --git a/pom.xml b/pom.xml
index c5c281a..f2ffabf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -65,6 +65,7 @@
<module>shell</module>
<module>hazelcast</module>
<module>utils</module>
+ <module>log</module>
<module>http</module>
<module>cloud</module>
<module>kubernetes</module>