--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.core.util.logs;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.LoggerLevel;
+
+class DefaultProfiler extends Profiler {
+
+ private static final String CONTEXT_SEPARATOR = " | ";
+
+ private final LinkedHashMap<String, Object> context = new LinkedHashMap<>();
+ private final Logger logger;
+
+ private long startTime = 0L;
+ private String startMessage = null;
+
+ public DefaultProfiler(Logger logger) {
+ this.logger = logger;
+ }
+
+ @Override
+ public boolean isDebugEnabled() {
+ return logger.isDebugEnabled();
+ }
+
+ @Override
+ public boolean isTraceEnabled() {
+ return logger.isTraceEnabled();
+ }
+
+ @Override
+ public Profiler start() {
+ this.startTime = System2.INSTANCE.now();
+ this.startMessage = null;
+ return this;
+ }
+
+ @Override
+ public Profiler startTrace(String message) {
+ this.startTime = System2.INSTANCE.now();
+ this.startMessage = message;
+ StringBuilder sb = new StringBuilder();
+ sb.append(message);
+ appendContext(sb);
+ logger.trace(sb.toString());
+ return this;
+ }
+
+ @Override
+ public Profiler startDebug(String message) {
+ this.startTime = System2.INSTANCE.now();
+ this.startMessage = message;
+ StringBuilder sb = new StringBuilder();
+ sb.append(message);
+ appendContext(sb);
+ logger.debug(sb.toString());
+ return this;
+ }
+
+ @Override
+ public Profiler startInfo(String message) {
+ this.startTime = System2.INSTANCE.now();
+ this.startMessage = message;
+ StringBuilder sb = new StringBuilder();
+ sb.append(message);
+ appendContext(sb);
+ logger.info(sb.toString());
+ return this;
+ }
+
+
+ @Override
+ public long stopTrace() {
+ return doStopWithoutMessage(LoggerLevel.TRACE);
+ }
+
+ @Override
+ public long stopDebug() {
+ return doStopWithoutMessage(LoggerLevel.DEBUG);
+ }
+
+ @Override
+ public long stopInfo() {
+ return doStopWithoutMessage(LoggerLevel.INFO);
+ }
+
+ private long doStopWithoutMessage(LoggerLevel level) {
+ if (startMessage == null) {
+ throw new IllegalStateException("Profiler#stopXXX() can't be called without any message defined in start methods");
+ }
+ return doStop(level, startMessage, " (done)");
+ }
+
+ @Override
+ public long stopTrace(String message) {
+ return doStop(LoggerLevel.TRACE, message, "");
+ }
+
+ @Override
+ public long stopDebug(String message) {
+ return doStop(LoggerLevel.DEBUG, message, "");
+ }
+
+ @Override
+ public long stopInfo(String message) {
+ return doStop(LoggerLevel.INFO, message, "");
+ }
+
+ private long doStop(LoggerLevel level, @Nullable String message, String messageSuffix) {
+ if (startTime == 0L) {
+ throw new IllegalStateException("Profiler must be started before being stopped");
+ }
+ long duration = System2.INSTANCE.now() - startTime;
+ StringBuilder sb = new StringBuilder();
+ if (!StringUtils.isEmpty(message)) {
+ sb.append(message);
+ sb.append(messageSuffix);
+ sb.append(CONTEXT_SEPARATOR);
+ }
+ sb.append("time=").append(duration).append("ms");
+ appendContext(sb);
+ log(level, sb.toString());
+ startTime = 0L;
+ startMessage = null;
+ context.clear();
+ return duration;
+ }
+
+ void log(LoggerLevel level, String msg) {
+ switch (level) {
+ case TRACE:
+ logger.trace(msg);
+ break;
+ case DEBUG:
+ logger.debug(msg);
+ break;
+ case INFO:
+ logger.info(msg);
+ break;
+ case WARN:
+ logger.warn(msg);
+ break;
+ case ERROR:
+ logger.error(msg);
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported LoggerLevel value: " + level);
+ }
+ }
+
+ private void appendContext(StringBuilder sb) {
+ for (Map.Entry<String, Object> entry : context.entrySet()) {
+ if (sb.length() > 0) {
+ sb.append(CONTEXT_SEPARATOR);
+ }
+ sb.append(entry.getKey()).append("=").append(Objects.toString(entry.getValue()));
+ }
+ }
+
+ @Override
+ public Profiler addContext(String key, @Nullable Object value) {
+ if (value == null) {
+ context.remove(key);
+ } else {
+ context.put(key, value);
+ }
+ return this;
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.core.util.logs;
+
+import javax.annotation.Nullable;
+
+class NullProfiler extends Profiler {
+
+ static final NullProfiler NULL_INSTANCE = new NullProfiler();
+
+ private NullProfiler() {
+ }
+
+ @Override
+ public boolean isDebugEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isTraceEnabled() {
+ return false;
+ }
+
+ @Override
+ public Profiler start() {
+ return this;
+ }
+
+ @Override
+ public Profiler startTrace(String message) {
+ return this;
+ }
+
+ @Override
+ public Profiler startDebug(String message) {
+ return this;
+ }
+
+ @Override
+ public Profiler startInfo(String message) {
+ return this;
+ }
+
+ @Override
+ public long stopTrace() {
+ return 0;
+ }
+
+ @Override
+ public long stopDebug() {
+ return 0;
+ }
+
+ @Override
+ public long stopInfo() {
+ return 0;
+ }
+
+ @Override
+ public long stopTrace(String message) {
+ return 0;
+ }
+
+ @Override
+ public long stopDebug(String message) {
+ return 0;
+ }
+
+ @Override
+ public long stopInfo(String message) {
+ return 0;
+ }
+
+ @Override
+ public Profiler addContext(String key, @Nullable Object value) {
+ // nothing to do
+ return this;
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.core.util.logs;
+
+import javax.annotation.Nullable;
+import org.sonar.api.utils.log.Logger;
+
+/**
+ *
+ * @since 5.1
+ */
+public abstract class Profiler {
+
+ public static Profiler create(Logger logger) {
+ return new DefaultProfiler(logger);
+ }
+
+ public static Profiler createIfTrace(Logger logger) {
+ if (logger.isTraceEnabled()) {
+ return create(logger);
+ }
+ return NullProfiler.NULL_INSTANCE;
+ }
+
+ public static Profiler createIfDebug(Logger logger) {
+ if (logger.isDebugEnabled()) {
+ return create(logger);
+ }
+ return NullProfiler.NULL_INSTANCE;
+ }
+
+ public abstract boolean isDebugEnabled();
+
+ public abstract boolean isTraceEnabled();
+
+ public abstract Profiler start();
+
+ public abstract Profiler startTrace(String message);
+
+ public abstract Profiler startDebug(String message);
+
+ public abstract Profiler startInfo(String message);
+
+ /**
+ * Works only if a message have been set in startXXX() methods.
+ */
+ public abstract long stopTrace();
+
+ public abstract long stopDebug();
+
+ public abstract long stopInfo();
+
+ public abstract long stopTrace(String message);
+
+ public abstract long stopDebug(String message);
+
+ public abstract long stopInfo(String message);
+
+ /**
+ * Context information is removed if value is <code>null</code>.
+ */
+ public abstract Profiler addContext(String key, @Nullable Object value);
+
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.core.util.logs;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.api.utils.log.Loggers;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+
+public class DefaultProfilerTest {
+
+ @Rule
+ public LogTester tester = new LogTester();
+
+ Profiler underTest = Profiler.create(Loggers.get("DefaultProfilerTest"));
+
+ @Test
+ public void test_levels() throws Exception {
+ // trace by default
+ assertThat(underTest.isDebugEnabled()).isTrue();
+ assertThat(underTest.isTraceEnabled()).isTrue();
+
+ tester.setLevel(LoggerLevel.DEBUG);
+ assertThat(underTest.isDebugEnabled()).isTrue();
+ assertThat(underTest.isTraceEnabled()).isFalse();
+
+ tester.setLevel(LoggerLevel.INFO);
+ assertThat(underTest.isDebugEnabled()).isFalse();
+ assertThat(underTest.isTraceEnabled()).isFalse();
+ }
+
+ @Test
+ public void stop_reuses_start_message() throws InterruptedException {
+ tester.setLevel(LoggerLevel.TRACE);
+
+ // trace
+ underTest.startTrace("Register rules");
+ Thread.sleep(2);
+ assertThat(tester.logs()).containsOnly("Register rules");
+ long timing = underTest.stopTrace();
+ assertThat(timing).isGreaterThan(0);
+ assertThat(tester.logs()).hasSize(2);
+ assertThat(tester.logs().get(1)).startsWith("Register rules (done) | time=" + timing);
+ tester.clear();
+
+ // debug
+ underTest.startDebug("Register rules");
+ Thread.sleep(2);
+ assertThat(tester.logs()).containsOnly("Register rules");
+ timing = underTest.stopTrace();
+ assertThat(timing).isGreaterThan(0);
+ assertThat(tester.logs()).hasSize(2);
+ assertThat(tester.logs().get(1)).startsWith("Register rules (done) | time=" + timing);
+ tester.clear();
+
+ // info
+ underTest.startInfo("Register rules");
+ Thread.sleep(2);
+ assertThat(tester.logs()).containsOnly("Register rules");
+ timing = underTest.stopTrace();
+ assertThat(timing).isGreaterThan(0);
+ assertThat(tester.logs()).hasSize(2);
+ assertThat(tester.logs().get(1)).startsWith("Register rules (done) | time=" + timing);
+ }
+
+ @Test
+ public void different_start_and_stop_messages() {
+ tester.setLevel(LoggerLevel.TRACE);
+
+ // start TRACE and stop DEBUG
+ underTest.startTrace("Register rules");
+ underTest.stopDebug("Rules registered");
+ assertThat(tester.logs()).hasSize(2);
+ assertThat(tester.logs().get(0)).contains("Register rules");
+ assertThat(tester.logs().get(1)).startsWith("Rules registered | time=");
+ tester.clear();
+
+ // start DEBUG and stop INFO
+ underTest.startDebug("Register rules");
+ underTest.stopInfo("Rules registered");
+ assertThat(tester.logs()).hasSize(2);
+ assertThat(tester.logs().get(0)).contains("Register rules");
+ assertThat(tester.logs().get(1)).startsWith("Rules registered | time=");
+ tester.clear();
+
+ // start INFO and stop TRACE
+ underTest.startInfo("Register rules");
+ underTest.stopTrace("Rules registered");
+ assertThat(tester.logs()).hasSize(2);
+ assertThat(tester.logs().get(0)).contains("Register rules");
+ assertThat(tester.logs().get(1)).startsWith("Rules registered | time=");
+ }
+
+ @Test
+ public void log_on_at_stop() {
+ tester.setLevel(LoggerLevel.TRACE);
+
+ // trace
+ underTest.start();
+ underTest.stopTrace("Rules registered");
+ assertThat(tester.logs()).hasSize(1);
+ assertThat(tester.logs().get(0)).startsWith("Rules registered | time=");
+ tester.clear();
+
+ // debug
+ underTest.start();
+ underTest.stopDebug("Rules registered");
+ assertThat(tester.logs()).hasSize(1);
+ assertThat(tester.logs().get(0)).startsWith("Rules registered | time=");
+ tester.clear();
+
+ // info
+ underTest.start();
+ underTest.stopInfo("Rules registered");
+ assertThat(tester.logs()).hasSize(1);
+ assertThat(tester.logs().get(0)).startsWith("Rules registered | time=");
+ }
+
+ @Test
+ public void add_context() {
+ org.sonar.core.util.logs.Profiler profiler = Profiler.create(Loggers.get("DefaultProfilerTest"));
+ profiler.addContext("a_string", "bar");
+ profiler.addContext("null_value", null);
+ profiler.addContext("an_int", 42);
+ profiler.start();
+ // do not write context as there's no message
+ assertThat(tester.logs()).isEmpty();
+
+ profiler.addContext("after_start", true);
+ profiler.stopInfo("Rules registered");
+ assertThat(tester.logs()).hasSize(1);
+ assertThat(tester.logs().get(0))
+ .startsWith("Rules registered | time=")
+ .endsWith("ms | a_string=bar | an_int=42 | after_start=true");
+ }
+
+ @Test
+ public void empty_message() {
+ underTest.addContext("foo", "bar");
+ underTest.startInfo("");
+ assertThat(tester.logs()).containsOnly("foo=bar");
+
+ underTest.addContext("after_start", true);
+ underTest.stopInfo("");
+ assertThat(tester.logs()).hasSize(2);
+ assertThat(tester.logs().get(1))
+ .startsWith("time=")
+ .endsWith("ms | foo=bar | after_start=true");
+ }
+
+ @Test
+ public void fail_if_stop_without_message() {
+ underTest.start();
+ try {
+ underTest.stopInfo();
+ fail();
+ } catch (IllegalStateException e) {
+ assertThat(e).hasMessage("Profiler#stopXXX() can't be called without any message defined in start methods");
+ }
+ }
+
+ @Test
+ public void fail_if_stop_without_start() {
+ try {
+ underTest.stopDebug("foo");
+ fail();
+ } catch (IllegalStateException e) {
+ assertThat(e).hasMessage("Profiler must be started before being stopped");
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.core.util.logs;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class NullProfilerTest {
+
+ NullProfiler underTest = NullProfiler.NULL_INSTANCE;
+
+ @Test
+ public void do_not_fail() {
+ assertThat(underTest.start()).isSameAs(underTest);
+ assertThat(underTest.startTrace("")).isSameAs(underTest);
+ assertThat(underTest.startDebug("")).isSameAs(underTest);
+ assertThat(underTest.startInfo("")).isSameAs(underTest);
+
+ assertThat(underTest.isDebugEnabled()).isFalse();
+ assertThat(underTest.isTraceEnabled()).isFalse();
+ assertThat(underTest.addContext("foo", "bar")).isSameAs(underTest);
+ }
+
+ @Test
+ public void stop_methods_returns_0() {
+ underTest.start();
+ assertThat(underTest.stopInfo()).isEqualTo(0);
+ assertThat(underTest.stopInfo("msg")).isEqualTo(0);
+ assertThat(underTest.stopDebug()).isEqualTo(0);
+ assertThat(underTest.stopDebug("msg")).isEqualTo(0);
+ assertThat(underTest.stopTrace()).isEqualTo(0);
+ assertThat(underTest.stopTrace("msg")).isEqualTo(0);
+
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.core.util.logs;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.api.utils.log.Loggers;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ProfilerTest {
+ @Rule
+ public LogTester tester = new LogTester();
+
+ @Test
+ public void create() {
+ Profiler profiler = Profiler.create(Loggers.get("foo"));
+ assertThat(profiler).isInstanceOf(DefaultProfiler.class);
+ }
+
+ @Test
+ public void create_null_profiler_if_trace_level_is_disabled() {
+ tester.setLevel(LoggerLevel.TRACE);
+ Profiler profiler = Profiler.createIfTrace(Loggers.get("foo"));
+ assertThat(profiler).isInstanceOf(DefaultProfiler.class);
+
+ tester.setLevel(LoggerLevel.DEBUG);
+ profiler = Profiler.createIfTrace(Loggers.get("foo"));
+ assertThat(profiler).isInstanceOf(NullProfiler.class);
+ }
+
+ @Test
+ public void create_null_profiler_if_debug_level_is_disabled() {
+ tester.setLevel(LoggerLevel.TRACE);
+ Profiler profiler = Profiler.createIfDebug(Loggers.get("foo"));
+ assertThat(profiler).isInstanceOf(DefaultProfiler.class);
+
+ tester.setLevel(LoggerLevel.INFO);
+ profiler = Profiler.createIfDebug(Loggers.get("foo"));
+ assertThat(profiler).isInstanceOf(NullProfiler.class);
+ }
+}