From 070351016e57a1b08d9b7d8100005939fb195b03 Mon Sep 17 00:00:00 2001 From: wallace Date: Mon, 28 Dec 2020 13:00:20 +0800 Subject: [PATCH 1/8] add aggregated logger: eventlogger and statlogger. --- .gitignore | 1 + README.md | 2 +- api/BUILD | 11 ++ .../flogger/AbstractAggregatedLogger.java | 15 ++ .../google/common/flogger/AbstractLogger.java | 72 +------- .../common/flogger/AbstractMessageLogger.java | 103 +++++++++++ .../common/flogger/AggregatedLogContext.java | 160 ++++++++++++++++++ .../common/flogger/AggregatedLoggingApi.java | 27 +++ .../google/common/flogger/DefaultLogData.java | 113 +++++++++++++ .../common/flogger/EventAggregator.java | 84 +++++++++ .../flogger/FluentAggregatedLogger.java | 69 ++++++++ .../google/common/flogger/FluentLogger.java | 4 +- .../com/google/common/flogger/LogContext.java | 123 +------------- .../common/flogger/MutableMetadata.java | 133 +++++++++++++++ .../google/common/flogger/StatAggregator.java | 82 +++++++++ .../common/flogger/backend/LoggerBackend.java | 2 +- .../common/flogger/backend/Platform.java | 4 +- .../system/StackBasedCallerFinder.java | 3 +- .../FluentAggregatedLoggerExample.java | 37 ++++ .../flogger/FluentAggregatedLoggerTest.java | 18 ++ .../common/flogger/testing/TestLogger.java | 4 +- .../common/flogger/GoogleLogContext.java | 2 +- .../google/common/flogger/GoogleLogger.java | 4 +- 23 files changed, 871 insertions(+), 202 deletions(-) create mode 100644 api/src/main/java/com/google/common/flogger/AbstractAggregatedLogger.java create mode 100644 api/src/main/java/com/google/common/flogger/AbstractMessageLogger.java create mode 100644 api/src/main/java/com/google/common/flogger/AggregatedLogContext.java create mode 100644 api/src/main/java/com/google/common/flogger/AggregatedLoggingApi.java create mode 100644 api/src/main/java/com/google/common/flogger/DefaultLogData.java create mode 100644 api/src/main/java/com/google/common/flogger/EventAggregator.java create mode 100644 api/src/main/java/com/google/common/flogger/FluentAggregatedLogger.java create mode 100644 api/src/main/java/com/google/common/flogger/MutableMetadata.java create mode 100644 api/src/main/java/com/google/common/flogger/StatAggregator.java create mode 100644 api/src/main/java/com/google/common/flogger/example/FluentAggregatedLoggerExample.java create mode 100644 api/src/test/java/com/google/common/flogger/FluentAggregatedLoggerTest.java diff --git a/.gitignore b/.gitignore index f83d40cc..98e981ec 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ *~ .DS_Store /bazel-* +.ijwb diff --git a/README.md b/README.md index 3534be5e..1c6cdba6 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ be `runtime` scope. ### 2. Add an import for [`FluentLogger`] ```java -import com.google.common.flogger.FluentLogger; + ``` ### 3. Create a `private static final` instance diff --git a/api/BUILD b/api/BUILD index 3d4b9d0f..aa0c7e42 100644 --- a/api/BUILD +++ b/api/BUILD @@ -137,6 +137,17 @@ java_binary( deps = ["@google_bazel_common//third_party/java/asm"], ) +java_binary( + name = "fluent_aggregated_logger_example", + srcs = ["src/main/java/com/google/common/flogger/example/FluentAggregatedLoggerExample.java"], + main_class = "com.google.common.flogger.example.FluentAggregatedLoggerExample", + deps = [ + ":api", + ":system_backend", + "@google_bazel_common//third_party/java/log4j", + ], +) + genrule( name = "gen_platform_provider", outs = ["platform_provider.jar"], diff --git a/api/src/main/java/com/google/common/flogger/AbstractAggregatedLogger.java b/api/src/main/java/com/google/common/flogger/AbstractAggregatedLogger.java new file mode 100644 index 00000000..b5bfcbb3 --- /dev/null +++ b/api/src/main/java/com/google/common/flogger/AbstractAggregatedLogger.java @@ -0,0 +1,15 @@ +package com.google.common.flogger; + +import com.google.common.flogger.backend.LoggerBackend; + +/** + * @Desctiption + * @Author wallace + * @Date 2020/12/26 + */ +public abstract class AbstractAggregatedLogger extends AbstractLogger { + + protected AbstractAggregatedLogger(LoggerBackend backend) { + super(backend); + } +} diff --git a/api/src/main/java/com/google/common/flogger/AbstractLogger.java b/api/src/main/java/com/google/common/flogger/AbstractLogger.java index bfc0fdce..a5aab927 100644 --- a/api/src/main/java/com/google/common/flogger/AbstractLogger.java +++ b/api/src/main/java/com/google/common/flogger/AbstractLogger.java @@ -16,22 +16,21 @@ package com.google.common.flogger; -import static com.google.common.flogger.util.Checks.checkNotNull; - import com.google.common.flogger.backend.LogData; import com.google.common.flogger.backend.LoggerBackend; import com.google.common.flogger.backend.LoggingException; import com.google.errorprone.annotations.CheckReturnValue; + import java.util.logging.Level; +import static com.google.common.flogger.util.Checks.checkNotNull; + /** * Base class for the fluent logger API. This class is a factory for instances of a fluent logging * API, used to build log statements via method chaining. - * - * @param the logging API provided by this logger. */ @CheckReturnValue -public abstract class AbstractLogger> { +public abstract class AbstractLogger { private final LoggerBackend backend; /** @@ -43,61 +42,6 @@ protected AbstractLogger(LoggerBackend backend) { this.backend = checkNotNull(backend, "backend"); } - // ---- PUBLIC API ---- - - /** - * Returns a fluent logging API appropriate for the specified log level. - *

- * If a logger implementation determines that logging is definitely disabled at this point then - * this method is expected to return a "no-op" implementation of that logging API, which will - * result in all further calls made for the log statement to being silently ignored. - *

- * A simple implementation of this method in a concrete subclass might look like: - *

{@code
-   * boolean isLoggable = isLoggable(level);
-   * boolean isForced = Platform.shouldForceLogging(getName(), level, isLoggable);
-   * return (isLoggable | isForced) ? new SubContext(level, isForced) : NO_OP;
-   * }
- * where {@code NO_OP} is a singleton, no-op instance of the logging API whose methods do nothing - * and just {@code return noOp()}. - */ - public abstract API at(Level level); - - /** A convenience method for at({@link Level#SEVERE}). */ - public final API atSevere() { - return at(Level.SEVERE); - } - - /** A convenience method for at({@link Level#WARNING}). */ - public final API atWarning() { - return at(Level.WARNING); - } - - /** A convenience method for at({@link Level#INFO}). */ - public final API atInfo() { - return at(Level.INFO); - } - - /** A convenience method for at({@link Level#CONFIG}). */ - public final API atConfig() { - return at(Level.CONFIG); - } - - /** A convenience method for at({@link Level#FINE}). */ - public final API atFine() { - return at(Level.FINE); - } - - /** A convenience method for at({@link Level#FINER}). */ - public final API atFiner() { - return at(Level.FINER); - } - - /** A convenience method for at({@link Level#FINEST}). */ - public final API atFinest() { - return at(Level.FINEST); - } - // ---- HELPER METHODS (useful during sub-class initialization) ---- /** @@ -115,14 +59,6 @@ protected String getName() { return backend.getLoggerName(); } - /** - * Returns whether the given level is enabled for this logger. Users wishing to guard code with a - * check for "loggability" should use {@code logger.atLevel().isEnabled()} instead. - */ - protected final boolean isLoggable(Level level) { - return backend.isLoggable(level); - } - // ---- IMPLEMENTATION DETAIL (only visible to the base logging context) ---- /** diff --git a/api/src/main/java/com/google/common/flogger/AbstractMessageLogger.java b/api/src/main/java/com/google/common/flogger/AbstractMessageLogger.java new file mode 100644 index 00000000..51ef2113 --- /dev/null +++ b/api/src/main/java/com/google/common/flogger/AbstractMessageLogger.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2012 The Flogger Authors. + * + * 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 com.google.common.flogger; + +import static com.google.common.flogger.util.Checks.checkNotNull; + +import com.google.common.flogger.backend.LogData; +import com.google.common.flogger.backend.LoggerBackend; +import com.google.common.flogger.backend.LoggingException; +import com.google.errorprone.annotations.CheckReturnValue; +import java.util.logging.Level; + +/** + * Base class for the fluent logger API. This class is a factory for instances of a fluent logging + * API, used to build log statements via method chaining. + * + * @param the logging API provided by this logger. + */ +@CheckReturnValue +public abstract class AbstractMessageLogger> extends AbstractLogger{ + + /** + * Constructs a new logger for the specified backend. + * + * @param backend the logger backend which ultimately writes the log statements out. + */ + protected AbstractMessageLogger(LoggerBackend backend) { + super(backend); + } + + // ---- PUBLIC API ---- + + /** + * Returns a fluent logging API appropriate for the specified log level. + *

+ * If a logger implementation determines that logging is definitely disabled at this point then + * this method is expected to return a "no-op" implementation of that logging API, which will + * result in all further calls made for the log statement to being silently ignored. + *

+ * A simple implementation of this method in a concrete subclass might look like: + *

{@code
+   * boolean isLoggable = isLoggable(level);
+   * boolean isForced = Platform.shouldForceLogging(getName(), level, isLoggable);
+   * return (isLoggable | isForced) ? new SubContext(level, isForced) : NO_OP;
+   * }
+ * where {@code NO_OP} is a singleton, no-op instance of the logging API whose methods do nothing + * and just {@code return noOp()}. + */ + public abstract API at(Level level); + + /** A convenience method for at({@link Level#SEVERE}). */ + public final API atSevere() { + return at(Level.SEVERE); + } + + /** A convenience method for at({@link Level#WARNING}). */ + public final API atWarning() { + return at(Level.WARNING); + } + + /** A convenience method for at({@link Level#INFO}). */ + public final API atInfo() { + return at(Level.INFO); + } + + /** A convenience method for at({@link Level#CONFIG}). */ + public final API atConfig() { + return at(Level.CONFIG); + } + + /** A convenience method for at({@link Level#FINE}). */ + public final API atFine() { + return at(Level.FINE); + } + + /** A convenience method for at({@link Level#FINER}). */ + public final API atFiner() { + return at(Level.FINER); + } + + /** A convenience method for at({@link Level#FINEST}). */ + public final API atFinest() { + return at(Level.FINEST); + } + + protected final boolean isLoggable(Level level) { + return getBackend().isLoggable(level); + } +} diff --git a/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java b/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java new file mode 100644 index 00000000..99448e87 --- /dev/null +++ b/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java @@ -0,0 +1,160 @@ +package com.google.common.flogger; + +import static com.google.common.flogger.util.Checks.*; + +import com.google.common.flogger.backend.LogData; +import com.google.common.flogger.backend.Metadata; +import com.google.common.flogger.backend.Platform; +import com.google.common.flogger.backend.TemplateContext; +import com.google.common.flogger.parser.DefaultPrintfMessageParser; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +/** + * @Desctiption + * @Author wallace + * @Date 2020/12/26 + */ +public abstract class AggregatedLogContext implements AggregatedLoggingApi { + + public static final class Key { + private Key() {} + public static final MetadataKey TIME_WINDOW = + MetadataKey.single("time_window", Integer.class); + + public static final MetadataKey NUMBER_WINDOW = + MetadataKey.single("number_window", Integer.class); + } + + private final class LogFlusher implements Runnable { + @Override + public void run() { + log(); + } + } + + private AtomicLong counter = new AtomicLong(0); + private AtomicBoolean flushLock = new AtomicBoolean(false); + private LogFlusher flusher; + private final MutableMetadata metadata = new MutableMetadata(); + + protected final String name; + protected final FluentAggregatedLogger logger; + protected final LogSite logSite; + protected final ScheduledExecutorService pool; + + protected abstract API self(); + protected abstract boolean haveData(); + protected abstract String message(); + + protected AggregatedLogContext(String name, FluentAggregatedLogger logger, LogSite logSite, ScheduledExecutorService pool){ + this.name = checkNotNull(name, "name"); + this.logger = checkNotNull(logger, "logger"); + this.logSite = checkNotNull(logSite, "logSite"); + this.pool = checkNotNull(pool, "pool"); + } + + private void start(long period){ + flusher = new LogFlusher(); + pool.scheduleAtFixedRate(flusher, 2, period, TimeUnit.SECONDS); + } + + @Override + public synchronized API timeWindow(int seconds) { + if(flusher != null){ + throw new RuntimeException("Do not set time window repeatedly"); + } + + metadata.addValue(Key.TIME_WINDOW, seconds); //just for logger backend to print CONTEXT + start(seconds); + + return self(); + } + + @Override + public API numberWindow(int number) { + metadata.addValue(Key.NUMBER_WINDOW, number); + return self(); + } + + protected Metadata getMetadata() { + return metadata != null ? metadata : Metadata.empty(); + + } + + protected FluentAggregatedLogger getLogger() { + return logger; + } + + protected void increaseCounter(){ + increaseCounter(0); + } + + protected void increaseCounter(int delta){ + counter.addAndGet(delta); + } + + protected boolean shouldFlush() { + //check number window + Long currentCounter = counter.get(); + int numberWindows = metadata.findValue(Key.NUMBER_WINDOW); + + //No need to check if currentCounter < 0 + if(currentCounter % numberWindows == 0){ + return true; + } + + return false; + } + + protected LogData data() { + long timestampNanos = Platform.getCurrentTimeNanos(); + String loggerName = getLogger().getBackend().getLoggerName(); + + DefaultLogData logData = new DefaultLogData(timestampNanos, loggerName); + logData.setMetadata(getMetadata()); + + logData.setLogSite(logSite); + + String message = message(); + logData.setTemplateContext(new TemplateContext(DefaultPrintfMessageParser.getInstance(), message)); + + //use empty array for avoiding null exception + logData.setArgs(new Object[]{}); + + return logData; + } + + protected void flush(){ + new Thread(new Runnable() { + @Override + public void run() { + log(); + } + }).start(); + } + + private void log(){ + if(flushLock.compareAndSet(false,true)) { + try { + do { + boolean have = haveData(); + + //even if there is no data to log, we still write a empty log to show timer is working. + LogData logData = data(); + getLogger().write(logData); + + if(!have){ + break; + } + }while (true); + } + finally { + flushLock.compareAndSet(true, false); + } + } + } +} diff --git a/api/src/main/java/com/google/common/flogger/AggregatedLoggingApi.java b/api/src/main/java/com/google/common/flogger/AggregatedLoggingApi.java new file mode 100644 index 00000000..4f34123d --- /dev/null +++ b/api/src/main/java/com/google/common/flogger/AggregatedLoggingApi.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2012 The Flogger Authors. + * + * 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 com.google.common.flogger; + +public interface AggregatedLoggingApi { + //void log(); + API timeWindow(int seconds); + API numberWindow(int number); + + //API add(String event, String content); + //API add(int perfValue); + //API inc(int delta); +} diff --git a/api/src/main/java/com/google/common/flogger/DefaultLogData.java b/api/src/main/java/com/google/common/flogger/DefaultLogData.java new file mode 100644 index 00000000..35064797 --- /dev/null +++ b/api/src/main/java/com/google/common/flogger/DefaultLogData.java @@ -0,0 +1,113 @@ +package com.google.common.flogger; + +import com.google.common.flogger.backend.LogData; +import com.google.common.flogger.backend.Metadata; +import com.google.common.flogger.backend.TemplateContext; + +import java.util.logging.Level; + +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +/** + * @Desctiption + * @Author wallace + * @Date 2020/12/26 + */ +public class DefaultLogData implements LogData { + private final Level level = Level.INFO; + /** The timestamp of the log statement that this context is associated with. */ + private final long timestampNanos; + + private final String loggerName; + + /** Additional metadata for this log statement (added via fluent API methods). */ + private Metadata metadata = null; + /** The log site information for this log statement (set immediately prior to post-processing). */ + private LogSite logSite = null; + /** The template context if formatting is required (set only after post-processing). */ + private TemplateContext templateContext = null; + /** The log arguments (set only after post-processing). */ + private Object[] args = null; + + public DefaultLogData(long timestampNanos, String loggerName){ + this.timestampNanos = timestampNanos; + this.loggerName = loggerName; + } + + public void setMetadata(Metadata metadata) { + this.metadata = metadata; + } + + public void setLogSite(LogSite logSite) { + this.logSite = logSite; + } + + public void setArgs(Object[] args) { + this.args = args; + } + + @Override + public Level getLevel() { + return level; + } + + @Override + public long getTimestampMicros() { + return NANOSECONDS.toMicros(timestampNanos); + } + + @Override + public long getTimestampNanos() { + return timestampNanos; + } + + @Override + public String getLoggerName() { + return loggerName; + } + + @Override + public LogSite getLogSite() { + if (logSite == null) { + throw new IllegalStateException("cannot request log site information prior to postProcess()"); + } + return logSite; + } + + public void setTemplateContext(TemplateContext templateContext) { + this.templateContext = templateContext; + } + + @Override + public Metadata getMetadata() { + return metadata != null ? metadata : Metadata.empty(); + } + + @Override + public boolean wasForced() { + // Check explicit TRUE here because findValue() can return null (which would fail unboxing). + return metadata != null && Boolean.TRUE.equals(metadata.findValue(LogContext.Key.WAS_FORCED)); + + } + + @Override + public TemplateContext getTemplateContext() { + return templateContext; + } + + @Override + public Object[] getArguments() { + if (templateContext == null) { + throw new IllegalStateException("cannot get arguments unless a template context exists"); + } + return args; + } + + @Override + public Object getLiteralArgument() { + if (templateContext != null) { + throw new IllegalStateException("cannot get literal argument if a template context exists"); + } + return args[0]; + } +} \ No newline at end of file diff --git a/api/src/main/java/com/google/common/flogger/EventAggregator.java b/api/src/main/java/com/google/common/flogger/EventAggregator.java new file mode 100644 index 00000000..4a055100 --- /dev/null +++ b/api/src/main/java/com/google/common/flogger/EventAggregator.java @@ -0,0 +1,84 @@ +package com.google.common.flogger; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ScheduledExecutorService; + +/** + * @Desctiption + * @Author wallace + * @Date 2020/12/26 + */ +public class EventAggregator extends AggregatedLogContext { + + static final class EventPair { + private final String key; + private final String value; + + public EventPair(String key, String value){ + this.key = key; + this.value = value; + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } + } + + protected final BlockingQueue eventList = + new LinkedBlockingQueue(1024 * 1024); + + public EventAggregator(String name, FluentAggregatedLogger logger, LogSite logSite, ScheduledExecutorService pool){ + super(name, logger, logSite, pool); + } + + @Override + protected EventAggregator self() { + return this; + } + + public void add(String event, String content){ + eventList.offer(new EventPair(event, content)); + increaseCounter(); + + if(shouldFlush()){ + flush(); + } + } + + @Override + protected boolean haveData() { + return !eventList.isEmpty(); + } + + @Override + protected String message(){ + List eventBuffer = new ArrayList(); + eventList.drainTo(eventBuffer, getMetadata().findValue(Key.NUMBER_WINDOW)); + + int bufferSize = eventBuffer.size(); + StringBuilder builder = new StringBuilder(); + builder.append(name).append("\n"); + for( int i = 0; i < bufferSize; i++){ + builder.append(eventBuffer.get(i).getKey()).append(":") + .append(eventBuffer.get(i).getValue()).append(" | "); + if((i+1) % 10 == 0){ + builder.append("\n"); + } + } + if(bufferSize % 10 != 0){ + builder.append("\n"); + } + + builder.append("\nthread:").append(Thread.currentThread().getId()); + builder.append(", total: ").append(bufferSize); + + return builder.toString(); + } +} diff --git a/api/src/main/java/com/google/common/flogger/FluentAggregatedLogger.java b/api/src/main/java/com/google/common/flogger/FluentAggregatedLogger.java new file mode 100644 index 00000000..bec7daec --- /dev/null +++ b/api/src/main/java/com/google/common/flogger/FluentAggregatedLogger.java @@ -0,0 +1,69 @@ +package com.google.common.flogger; + +import com.google.common.flogger.backend.LoggerBackend; +import com.google.common.flogger.backend.Platform; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +import static com.google.common.flogger.util.Checks.checkNotNull; + +/** + * @Desctiption + * @Author wallace + * @Date 2020/12/26 + */ +public final class FluentAggregatedLogger extends AbstractAggregatedLogger { + private static final ScheduledExecutorService pool = Executors.newScheduledThreadPool(8); + + private final Map aggregatorMap = new ConcurrentHashMap(); + + private FluentAggregatedLogger(LoggerBackend backend) { + super(backend); + } + + public static FluentAggregatedLogger forEnclosingClass() { + // NOTE: It is _vital_ that the call to "caller finder" is made directly inside the static + // factory method. See getCallerFinder() for more information. + String loggingClass = Platform.getCallerFinder().findLoggingClass(FluentAggregatedLogger.class); + return new FluentAggregatedLogger(Platform.getBackend(loggingClass)); + } + + public EventAggregator getEvent(String name ){ + AggregatedLogContext aggregator = aggregatorMap.get(name); + if(aggregator == null){ + //Get logsite here for async write data in new Thread + LogSite logSite = checkNotNull(Platform.getCallerFinder().findLogSite(FluentAggregatedLogger.class, 0), + "logger backend must not return a null LogSite"); + aggregator = new EventAggregator(name, this, logSite, pool); + aggregatorMap.putIfAbsent(name, aggregator); + aggregator = aggregatorMap.get(name); + } + + if( !(aggregator instanceof EventAggregator)){ + throw new RuntimeException("There is another kind of logger with the same name"); + } + + return (EventAggregator)aggregator; + } + + public StatAggregator getStat(String name ){ + AggregatedLogContext aggregator = aggregatorMap.get(name); + if(aggregator == null){ + //Get logsite here for async write data in new Thread + LogSite logSite = checkNotNull(Platform.getCallerFinder().findLogSite(FluentAggregatedLogger.class, 0), + "logger backend must not return a null LogSite"); + aggregator = new StatAggregator(name, this, logSite, pool); + aggregatorMap.putIfAbsent(name, aggregator); + aggregator = aggregatorMap.get(name); + } + + if( !(aggregator instanceof StatAggregator)){ + throw new RuntimeException("There is another kind of logger with the same name: " + name); + } + + return (StatAggregator)aggregator; + } +} diff --git a/api/src/main/java/com/google/common/flogger/FluentLogger.java b/api/src/main/java/com/google/common/flogger/FluentLogger.java index bd1a8f33..209327d7 100644 --- a/api/src/main/java/com/google/common/flogger/FluentLogger.java +++ b/api/src/main/java/com/google/common/flogger/FluentLogger.java @@ -24,7 +24,7 @@ import java.util.logging.Level; /** - * The default implementation of {@link AbstractLogger} which returns the basic {@link LoggingApi} + * The default implementation of {@link AbstractMessageLogger} which returns the basic {@link LoggingApi} * and uses the default parser and system configured backend. *

* Note that when extending the logging API or specifying a new parser, you will need to create a @@ -36,7 +36,7 @@ * a specific logger implementation always get the same behavior. */ @CheckReturnValue -public final class FluentLogger extends AbstractLogger { +public final class FluentLogger extends AbstractMessageLogger { /** * The non-wildcard, fully specified, logging API for this logger. Fluent logger implementations * should specify a non-wildcard API like this with which to generify the abstract logger. diff --git a/api/src/main/java/com/google/common/flogger/LogContext.java b/api/src/main/java/com/google/common/flogger/LogContext.java index 0d5b3336..ff4e0675 100644 --- a/api/src/main/java/com/google/common/flogger/LogContext.java +++ b/api/src/main/java/com/google/common/flogger/LogContext.java @@ -41,7 +41,7 @@ * This class is an implementation of the base {@link LoggingApi} interface and acts as a holder for * any state applied to the log statement during the fluent call sequence. The lifecycle of a * logging context is very short; it is created by a logger, usually in response to a call to the - * {@link AbstractLogger#at(Level)} method, and normally lasts only as long as the log statement. + * {@link AbstractMessageLogger#at(Level)} method, and normally lasts only as long as the log statement. *

* This class should not be visible to normal users of the logging API and it is only needed when * extending the API to add more functionality. In order to extend the logging API and add methods @@ -53,7 +53,7 @@ */ @CheckReturnValue public abstract class LogContext< - LOGGER extends AbstractLogger, API extends LoggingApi> + LOGGER extends AbstractMessageLogger, API extends LoggingApi> implements LoggingApi, LogData { /** @@ -148,125 +148,6 @@ public void emit(Tags tags, KeyValueHandler out) { MetadataKey.single("stack_size", StackSize.class); } - static final class MutableMetadata extends Metadata { - /** - * The default number of key/value pairs we initially allocate space for when someone adds - * metadata to this context. - *

- * Note: As of 10/12 the VM allocates small object arrays very linearly with respect to the - * number of elements (an array has a 12 byte header with 4 bytes/element for object - * references). The allocation size is always rounded up to the next 8 bytes which means we - * can just pick a small value for the initial size and grow from there without too much waste. - *

- * For 4 key/value pairs, we will need 8 elements in the array, which will take up 48 bytes - * {@code (12 + (8 * 4) = 44}, which when rounded up is 48. - */ - private static final int INITIAL_KEY_VALUE_CAPACITY = 4; - - /** - * The array of key/value pairs to hold any metadata the might be added by the logger or any of - * the fluent methods on our API. This is an array so it is as space efficient as possible. - */ - private Object[] keyValuePairs = new Object[2 * INITIAL_KEY_VALUE_CAPACITY]; - /** The number of key/value pairs currently stored in the array. */ - private int keyValueCount = 0; - - @Override - public int size() { - return keyValueCount; - } - - @Override - public MetadataKey getKey(int n) { - if (n >= keyValueCount) { - throw new IndexOutOfBoundsException(); - } - return (MetadataKey) keyValuePairs[2 * n]; - } - - @Override - public Object getValue(int n) { - if (n >= keyValueCount) { - throw new IndexOutOfBoundsException(); - } - return keyValuePairs[(2 * n) + 1]; - } - - private int indexOf(MetadataKey key) { - for (int index = 0; index < keyValueCount; index++) { - if (keyValuePairs[2 * index].equals(key)) { - return index; - } - } - return -1; - } - - @Override - @NullableDecl - public T findValue(MetadataKey key) { - int index = indexOf(key); - return index != -1 ? key.cast(keyValuePairs[(2 * index) + 1]) : null; - } - - /** - * Adds the key/value pair to the metadata (growing the internal array as necessary). If the - * key cannot be repeated, and there is already a value for the key in the metadata, then the - * existing value is replaced, otherwise the value is added at the end of the metadata. - */ - void addValue(MetadataKey key, T value) { - if (!key.canRepeat()) { - int index = indexOf(key); - if (index != -1) { - keyValuePairs[(2 * index) + 1] = checkNotNull(value, "metadata value"); - return; - } - } - // Check that the array is big enough for one more element. - if (2 * (keyValueCount + 1) > keyValuePairs.length) { - // Use doubling here (this code should almost never be hit in normal usage and the total - // number of items should always stay relatively small. If this resizing algorithm is ever - // modified it is vital that the new value is always an even number. - keyValuePairs = Arrays.copyOf(keyValuePairs, 2 * keyValuePairs.length); - } - keyValuePairs[2 * keyValueCount] = checkNotNull(key, "metadata key"); - keyValuePairs[(2 * keyValueCount) + 1] = checkNotNull(value, "metadata value"); - keyValueCount += 1; - } - - /** Removes all key/value pairs for a given key. */ - void removeAllValues(MetadataKey key) { - int index = indexOf(key); - if (index >= 0) { - int dest = 2 * index; - int src = dest + 2; - while (src < (2 * keyValueCount)) { - Object nextKey = keyValuePairs[src]; - if (!nextKey.equals(key)) { - keyValuePairs[dest] = nextKey; - keyValuePairs[dest + 1] = keyValuePairs[src + 1]; - dest += 2; - } - src += 2; - } - // We know src & dest are +ve and (src > dest), so shifting is safe here. - keyValueCount -= (src - dest) >> 1; - while (dest < src) { - keyValuePairs[dest++] = null; - } - } - } - - /** Strictly for debugging. */ - @Override - public String toString() { - StringBuilder out = new StringBuilder("Metadata{"); - for (int n = 0; n < size(); n++) { - out.append(" '").append(getKey(n)).append("': ").append(getValue(n)); - } - return out.append(" }").toString(); - } - } - /** * A simple token used to identify cases where a single literal value is logged. Note that this * instance must be unique and it is important not to replace this with {@code ""} or any other diff --git a/api/src/main/java/com/google/common/flogger/MutableMetadata.java b/api/src/main/java/com/google/common/flogger/MutableMetadata.java new file mode 100644 index 00000000..02775393 --- /dev/null +++ b/api/src/main/java/com/google/common/flogger/MutableMetadata.java @@ -0,0 +1,133 @@ +package com.google.common.flogger; + +import com.google.common.flogger.backend.Metadata; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +import java.util.Arrays; + +import static com.google.common.flogger.util.Checks.checkNotNull; + +/** + * @Desctiption + * @Author wallace + * @Date 2020/12/26 + */ +public class MutableMetadata extends Metadata { + + /** + * The default number of key/value pairs we initially allocate space for when someone adds + * metadata to this context. + *

+ * Note: As of 10/12 the VM allocates small object arrays very linearly with respect to the + * number of elements (an array has a 12 byte header with 4 bytes/element for object + * references). The allocation size is always rounded up to the next 8 bytes which means we + * can just pick a small value for the initial size and grow from there without too much waste. + *

+ * For 4 key/value pairs, we will need 8 elements in the array, which will take up 48 bytes + * {@code (12 + (8 * 4) = 44}, which when rounded up is 48. + */ + private static final int INITIAL_KEY_VALUE_CAPACITY = 4; + + /** + * The array of key/value pairs to hold any metadata the might be added by the logger or any of + * the fluent methods on our API. This is an array so it is as space efficient as possible. + */ + private Object[] keyValuePairs = new Object[2 * INITIAL_KEY_VALUE_CAPACITY]; + /** The number of key/value pairs currently stored in the array. */ + private int keyValueCount = 0; + + @Override + public int size() { + return keyValueCount; + } + + @Override + public MetadataKey getKey(int n) { + if (n >= keyValueCount) { + throw new IndexOutOfBoundsException(); + } + return (MetadataKey) keyValuePairs[2 * n]; + } + + @Override + public Object getValue(int n) { + if (n >= keyValueCount) { + throw new IndexOutOfBoundsException(); + } + return keyValuePairs[(2 * n) + 1]; + } + + private int indexOf(MetadataKey key) { + for (int index = 0; index < keyValueCount; index++) { + if (keyValuePairs[2 * index].equals(key)) { + return index; + } + } + return -1; + } + + @Override + @NullableDecl + public T findValue(MetadataKey key) { + int index = indexOf(key); + return index != -1 ? key.cast(keyValuePairs[(2 * index) + 1]) : null; + } + + /** + * Adds the key/value pair to the metadata (growing the internal array as necessary). If the + * key cannot be repeated, and there is already a value for the key in the metadata, then the + * existing value is replaced, otherwise the value is added at the end of the metadata. + */ + void addValue(MetadataKey key, T value) { + if (!key.canRepeat()) { + int index = indexOf(key); + if (index != -1) { + keyValuePairs[(2 * index) + 1] = checkNotNull(value, "metadata value"); + return; + } + } + // Check that the array is big enough for one more element. + if (2 * (keyValueCount + 1) > keyValuePairs.length) { + // Use doubling here (this code should almost never be hit in normal usage and the total + // number of items should always stay relatively small. If this resizing algorithm is ever + // modified it is vital that the new value is always an even number. + keyValuePairs = Arrays.copyOf(keyValuePairs, 2 * keyValuePairs.length); + } + keyValuePairs[2 * keyValueCount] = checkNotNull(key, "metadata key"); + keyValuePairs[(2 * keyValueCount) + 1] = checkNotNull(value, "metadata value"); + keyValueCount += 1; + } + + /** Removes all key/value pairs for a given key. */ + void removeAllValues(MetadataKey key) { + int index = indexOf(key); + if (index >= 0) { + int dest = 2 * index; + int src = dest + 2; + while (src < (2 * keyValueCount)) { + Object nextKey = keyValuePairs[src]; + if (!nextKey.equals(key)) { + keyValuePairs[dest] = nextKey; + keyValuePairs[dest + 1] = keyValuePairs[src + 1]; + dest += 2; + } + src += 2; + } + // We know src & dest are +ve and (src > dest), so shifting is safe here. + keyValueCount -= (src - dest) >> 1; + while (dest < src) { + keyValuePairs[dest++] = null; + } + } + } + + /** Strictly for debugging. */ + @Override + public String toString() { + StringBuilder out = new StringBuilder("Metadata{"); + for (int n = 0; n < size(); n++) { + out.append(" '").append(getKey(n)).append("': ").append(getValue(n)); + } + return out.append(" }").toString(); + } +} diff --git a/api/src/main/java/com/google/common/flogger/StatAggregator.java b/api/src/main/java/com/google/common/flogger/StatAggregator.java new file mode 100644 index 00000000..6f33290e --- /dev/null +++ b/api/src/main/java/com/google/common/flogger/StatAggregator.java @@ -0,0 +1,82 @@ +package com.google.common.flogger; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ScheduledExecutorService; + +/** + * @Desctiption + * @Author wallace + * @Date 2020/12/28 + */ +public class StatAggregator extends AggregatedLogContext { + + protected final BlockingQueue valueList = + new LinkedBlockingQueue(1024 * 1024); + + protected StatAggregator(String name, FluentAggregatedLogger logger, LogSite logSite, ScheduledExecutorService pool) { + super(name, logger, logSite, pool); + } + + @Override + protected StatAggregator self() { + return this; + } + + public void add(long value){ + valueList.offer(value); + increaseCounter(); + + if(shouldFlush()){ + flush(); + } + } + + @Override + protected boolean haveData() { + return !valueList.isEmpty(); + } + + @Override + protected String message() { + List valueBuffer = new ArrayList(); + valueList.drainTo(valueBuffer, getMetadata().findValue(Key.NUMBER_WINDOW)); + + long min = Long.MAX_VALUE; + long max = Long.MIN_VALUE; + long total = 0; + long avg = 0; + + for(Long e : valueBuffer){ + if(e > max){ + max = e; + } + + if(e < min){ + min = e; + } + + total += e; + } + + StringBuilder builder = new StringBuilder(); + builder.append(name).append("\n"); + + if(!valueBuffer.isEmpty()) { + avg = total / valueBuffer.size(); + + String sep = ", "; + builder.append("min:").append(min).append(sep) + .append("max:").append(max).append(sep) + .append("total:").append(total).append(sep) + .append("count:").append(valueBuffer.size()).append(sep) + .append("avg:").append(avg).append("."); + } else { + builder.append("no data\n"); + } + + return builder.toString(); + } +} diff --git a/api/src/main/java/com/google/common/flogger/backend/LoggerBackend.java b/api/src/main/java/com/google/common/flogger/backend/LoggerBackend.java index 688e9b90..c48c711a 100644 --- a/api/src/main/java/com/google/common/flogger/backend/LoggerBackend.java +++ b/api/src/main/java/com/google/common/flogger/backend/LoggerBackend.java @@ -22,7 +22,7 @@ * Interface for all logger backends. *

*

Implementation Notes:

- * Often each {@link com.google.common.flogger.AbstractLogger} instance will be instantiated with a + * Often each {@link com.google.common.flogger.AbstractMessageLogger} instance will be instantiated with a * new logger backend (to permit per-class logging behavior). Because of this it is important that * LoggerBackends have as little per-instance state as possible. */ diff --git a/api/src/main/java/com/google/common/flogger/backend/Platform.java b/api/src/main/java/com/google/common/flogger/backend/Platform.java index 635ae3fb..b1bcc7b0 100644 --- a/api/src/main/java/com/google/common/flogger/backend/Platform.java +++ b/api/src/main/java/com/google/common/flogger/backend/Platform.java @@ -18,7 +18,6 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; -import com.google.common.flogger.AbstractLogger; import com.google.common.flogger.LogSite; import com.google.common.flogger.context.ContextDataProvider; import com.google.common.flogger.context.Tags; @@ -139,7 +138,8 @@ public abstract static class LogCallerFinder { * @throws IllegalStateException if there was no caller of the specified logged passed on the * stack (which may occur if the logger class was invoked directly by JNI). */ - public abstract String findLoggingClass(Class> loggerClass); + //public abstract String findLoggingClass(Class> loggerClass); + public abstract String findLoggingClass(Class loggerClass); /** * Returns a LogSite found from the current stack trace for the caller of the log() method on diff --git a/api/src/main/java/com/google/common/flogger/backend/system/StackBasedCallerFinder.java b/api/src/main/java/com/google/common/flogger/backend/system/StackBasedCallerFinder.java index e4dd55b9..7947198c 100644 --- a/api/src/main/java/com/google/common/flogger/backend/system/StackBasedCallerFinder.java +++ b/api/src/main/java/com/google/common/flogger/backend/system/StackBasedCallerFinder.java @@ -16,7 +16,6 @@ package com.google.common.flogger.backend.system; -import com.google.common.flogger.AbstractLogger; import com.google.common.flogger.LogSite; import com.google.common.flogger.backend.Platform.LogCallerFinder; import com.google.common.flogger.util.CallerFinder; @@ -36,7 +35,7 @@ public static LogCallerFinder getInstance() { } @Override - public String findLoggingClass(Class> loggerClass) { + public String findLoggingClass(Class loggerClass) { // We can skip at most only 1 method from the analysis, the inferLoggingClass() method itself. StackTraceElement caller = CallerFinder.findCallerOf(loggerClass, new Throwable(), 1); if (caller != null) { diff --git a/api/src/main/java/com/google/common/flogger/example/FluentAggregatedLoggerExample.java b/api/src/main/java/com/google/common/flogger/example/FluentAggregatedLoggerExample.java new file mode 100644 index 00000000..6d9c99af --- /dev/null +++ b/api/src/main/java/com/google/common/flogger/example/FluentAggregatedLoggerExample.java @@ -0,0 +1,37 @@ +package com.google.common.flogger.example; + +import com.google.common.flogger.EventAggregator; +import com.google.common.flogger.FluentAggregatedLogger; +import com.google.common.flogger.FluentLogger; +import com.google.common.flogger.StatAggregator; + +import java.util.logging.Level; + +/** + * @Desctiption + * @Author wallace + * @Date 2020/12/26 + */ +public class FluentAggregatedLoggerExample { + final static FluentLogger logger1 = FluentLogger.forEnclosingClass(); + final static FluentAggregatedLogger logger2 = FluentAggregatedLogger.forEnclosingClass(); + + + public static void main(String[] args) throws InterruptedException { + logger1.at(Level.INFO).log("hello, world"); + + EventAggregator eventAggregator = logger2.getEvent("get-user-api-resp").timeWindow(5).numberWindow(20); + for(int i = 0; i < 92; i++) { + eventAggregator.add("requestId=10" + i, "200"); + } + + StatAggregator statAggregator = logger2.getStat("get-user-api-perf").timeWindow(5).numberWindow(10); + for(int i = 0; i < 100; i++) { + statAggregator.add(i); + } + + Thread.sleep(3000); + + statAggregator.add(100); + } +} diff --git a/api/src/test/java/com/google/common/flogger/FluentAggregatedLoggerTest.java b/api/src/test/java/com/google/common/flogger/FluentAggregatedLoggerTest.java new file mode 100644 index 00000000..71050e7d --- /dev/null +++ b/api/src/test/java/com/google/common/flogger/FluentAggregatedLoggerTest.java @@ -0,0 +1,18 @@ +package com.google.common.flogger; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * @Desctiption + * @Author wallace + * @Date 2020/12/26 + */ +@RunWith(JUnit4.class) +public class FluentAggregatedLoggerTest { + @Test + public void testRun(){ + + } +} diff --git a/api/src/test/java/com/google/common/flogger/testing/TestLogger.java b/api/src/test/java/com/google/common/flogger/testing/TestLogger.java index fb18c273..36d4fd20 100644 --- a/api/src/test/java/com/google/common/flogger/testing/TestLogger.java +++ b/api/src/test/java/com/google/common/flogger/testing/TestLogger.java @@ -16,7 +16,7 @@ package com.google.common.flogger.testing; -import com.google.common.flogger.AbstractLogger; +import com.google.common.flogger.AbstractMessageLogger; import com.google.common.flogger.LogContext; import com.google.common.flogger.LoggingApi; import com.google.common.flogger.backend.LoggerBackend; @@ -31,7 +31,7 @@ * *

This class is mutable and not thread safe. */ -public final class TestLogger extends AbstractLogger { +public final class TestLogger extends AbstractMessageLogger { // Midnight Jan 1st, 2000 (GMT) private static final long DEFAULT_TIMESTAMP_NANOS = 946684800000000000L; diff --git a/google/src/main/java/com/google/common/flogger/GoogleLogContext.java b/google/src/main/java/com/google/common/flogger/GoogleLogContext.java index 24dca3ee..57b34aa5 100644 --- a/google/src/main/java/com/google/common/flogger/GoogleLogContext.java +++ b/google/src/main/java/com/google/common/flogger/GoogleLogContext.java @@ -31,7 +31,7 @@ */ @CheckReturnValue public abstract class GoogleLogContext< - LOGGER extends AbstractLogger, API extends GoogleLoggingApi> + LOGGER extends AbstractMessageLogger, API extends GoogleLoggingApi> extends LogContext implements GoogleLoggingApi { /** diff --git a/google/src/main/java/com/google/common/flogger/GoogleLogger.java b/google/src/main/java/com/google/common/flogger/GoogleLogger.java index eefaf676..2110389e 100644 --- a/google/src/main/java/com/google/common/flogger/GoogleLogger.java +++ b/google/src/main/java/com/google/common/flogger/GoogleLogger.java @@ -24,11 +24,11 @@ import java.util.logging.Level; /** - * The default Google specific implementation of {@link AbstractLogger} which extends the core + * The default Google specific implementation of {@link AbstractMessageLogger} which extends the core * {@link LoggingApi} to add Google specific functionality. */ @CheckReturnValue -public final class GoogleLogger extends AbstractLogger { +public final class GoogleLogger extends AbstractMessageLogger { /** See {@link GoogleLoggingApi}. */ public interface Api extends GoogleLoggingApi {} From 59086baa29f8adc933647e267fd181c836352e61 Mon Sep 17 00:00:00 2001 From: wallace Date: Thu, 31 Dec 2020 11:28:52 +0800 Subject: [PATCH 2/8] add comment and optimize some detailed logic --- .../flogger/AbstractAggregatedLogger.java | 15 --- .../common/flogger/AggregatedLogContext.java | 114 ++++++++++++++++-- .../common/flogger/AggregatedLoggingApi.java | 29 ++++- .../google/common/flogger/DefaultLogData.java | 20 ++- .../common/flogger/EventAggregator.java | 80 +++++++++++- .../flogger/FluentAggregatedLogger.java | 54 +++++++-- .../google/common/flogger/StatAggregator.java | 111 +++++++++++++++-- .../FluentAggregatedLoggerExample.java | 29 +++-- 8 files changed, 394 insertions(+), 58 deletions(-) delete mode 100644 api/src/main/java/com/google/common/flogger/AbstractAggregatedLogger.java diff --git a/api/src/main/java/com/google/common/flogger/AbstractAggregatedLogger.java b/api/src/main/java/com/google/common/flogger/AbstractAggregatedLogger.java deleted file mode 100644 index b5bfcbb3..00000000 --- a/api/src/main/java/com/google/common/flogger/AbstractAggregatedLogger.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.google.common.flogger; - -import com.google.common.flogger.backend.LoggerBackend; - -/** - * @Desctiption - * @Author wallace - * @Date 2020/12/26 - */ -public abstract class AbstractAggregatedLogger extends AbstractLogger { - - protected AbstractAggregatedLogger(LoggerBackend backend) { - super(backend); - } -} diff --git a/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java b/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java index 99448e87..f69e36c7 100644 --- a/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java +++ b/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2012 The Flogger Authors. + * + * 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 com.google.common.flogger; import static com.google.common.flogger.util.Checks.*; @@ -7,24 +23,40 @@ import com.google.common.flogger.backend.Platform; import com.google.common.flogger.backend.TemplateContext; import com.google.common.flogger.parser.DefaultPrintfMessageParser; +import com.google.common.flogger.util.Checks; +import com.google.errorprone.annotations.CheckReturnValue; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; /** - * @Desctiption - * @Author wallace - * @Date 2020/12/26 + * Base class for the aggregated logger API. + * + * @param the {@link AbstractLogger} to write log data + * @param the {@link AggregatedLoggingApi} to format log data */ -public abstract class AggregatedLogContext implements AggregatedLoggingApi { + /** + * Available configuration Key + */ public static final class Key { private Key() {} + + /** + * Time window for aggregating log. + * {@link AbstractLogger} will log all data at the end of the time window. + */ public static final MetadataKey TIME_WINDOW = MetadataKey.single("time_window", Integer.class); + /** + * Number window for aggregating log. + * {@link AbstractLogger} will log data when the number of data is up to number window. + */ public static final MetadataKey NUMBER_WINDOW = MetadataKey.single("number_window", Integer.class); } @@ -36,10 +68,16 @@ public void run() { } } + //Counter for number window private AtomicLong counter = new AtomicLong(0); - private AtomicBoolean flushLock = new AtomicBoolean(false); + + //Runnable for time window private LogFlusher flusher; - private final MutableMetadata metadata = new MutableMetadata(); + + //Only one thread will flush log + private AtomicBoolean flushLock = new AtomicBoolean(false); + + protected final MutableMetadata metadata = new MutableMetadata(); protected final String name; protected final FluentAggregatedLogger logger; @@ -47,9 +85,29 @@ public void run() { protected final ScheduledExecutorService pool; protected abstract API self(); + + /** + * Check if there are some data to log. + * + * @return the boolean + */ protected abstract boolean haveData(); + + /** + * Format aggregated data to string for logging. + * + * @return the string + */ protected abstract String message(); + /** + * Instantiates a new AggregatedLogContext. + * + * @param name the name + * @param logger the logger (see {@link FluentAggregatedLogger}). + * @param logSite the log site (see {@link LogSite}). + * @param pool the executor service pool used to periodically log data. + */ protected AggregatedLogContext(String name, FluentAggregatedLogger logger, LogSite logSite, ScheduledExecutorService pool){ this.name = checkNotNull(name, "name"); this.logger = checkNotNull(logger, "logger"); @@ -57,6 +115,11 @@ protected AggregatedLogContext(String name, FluentAggregatedLogger logger, LogSi this.pool = checkNotNull(pool, "pool"); } + /** + * Schedule log flusher at fixed rate + * + * @param period + */ private void start(long period){ flusher = new LogFlusher(); pool.scheduleAtFixedRate(flusher, 2, period, TimeUnit.SECONDS); @@ -65,9 +128,11 @@ private void start(long period){ @Override public synchronized API timeWindow(int seconds) { if(flusher != null){ - throw new RuntimeException("Do not set time window repeatedly"); + return self(); } + Checks.checkArgument(seconds > 0, "Time window should be larger than 0"); + metadata.addValue(Key.TIME_WINDOW, seconds); //just for logger backend to print CONTEXT start(seconds); @@ -76,27 +141,53 @@ public synchronized API timeWindow(int seconds) { @Override public API numberWindow(int number) { + Checks.checkArgument(number > 0, "Number window should be larger than 0"); + metadata.addValue(Key.NUMBER_WINDOW, number); return self(); } + /** + * Gets metadata. + * + * @return the metadata + */ protected Metadata getMetadata() { return metadata != null ? metadata : Metadata.empty(); } + /** + * Gets logger. + * + * @return the logger + */ protected FluentAggregatedLogger getLogger() { return logger; } + /** + * Increase counter. + */ protected void increaseCounter(){ increaseCounter(0); } + /** + * Increase counter. + * + * @param delta the delta + */ protected void increaseCounter(int delta){ counter.addAndGet(delta); } + /** + * Check if it's time to flush data. Only for number window. + * No need to check for time window because executor service pool will periodically flush data. + * + * @return the boolean + */ protected boolean shouldFlush() { //check number window Long currentCounter = counter.get(); @@ -110,6 +201,11 @@ protected boolean shouldFlush() { return false; } + /** + * Generate {@link LogData} for logger backend. + * + * @return the log data + */ protected LogData data() { long timestampNanos = Platform.getCurrentTimeNanos(); String loggerName = getLogger().getBackend().getLoggerName(); @@ -128,6 +224,10 @@ protected LogData data() { return logData; } + /** + * Flush aggregated data in new Thread if number window is full. + * Please always call shouldFlush() first. + */ protected void flush(){ new Thread(new Runnable() { @Override diff --git a/api/src/main/java/com/google/common/flogger/AggregatedLoggingApi.java b/api/src/main/java/com/google/common/flogger/AggregatedLoggingApi.java index 4f34123d..70c31656 100644 --- a/api/src/main/java/com/google/common/flogger/AggregatedLoggingApi.java +++ b/api/src/main/java/com/google/common/flogger/AggregatedLoggingApi.java @@ -16,12 +16,31 @@ package com.google.common.flogger; +import com.google.errorprone.annotations.CheckReturnValue; + +/** + * The basic aggregated logging API. An implementation of this API (or an extension of it) will be + * returned by any {@link FluentAggregatedLogger}, and forms the basis of the call chain. + */ +@CheckReturnValue public interface AggregatedLoggingApi { - //void log(); + /** + * Set aggregated logger time window. + * If time window is set, aggregated logger will periodically flush log. + * + * @param seconds + * @return + */ API timeWindow(int seconds); - API numberWindow(int number); - //API add(String event, String content); - //API add(int perfValue); - //API inc(int delta); + /** + * Set aggregated logger number window. + * + * If number window is set, aggregated logger will flush log when log number + * is equal or more than number window. + * + * @param number + * @return + */ + API numberWindow(int number); } diff --git a/api/src/main/java/com/google/common/flogger/DefaultLogData.java b/api/src/main/java/com/google/common/flogger/DefaultLogData.java index 35064797..1868e0fe 100644 --- a/api/src/main/java/com/google/common/flogger/DefaultLogData.java +++ b/api/src/main/java/com/google/common/flogger/DefaultLogData.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2012 The Flogger Authors. + * + * 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 com.google.common.flogger; import com.google.common.flogger.backend.LogData; @@ -9,9 +25,7 @@ import static java.util.concurrent.TimeUnit.NANOSECONDS; /** - * @Desctiption - * @Author wallace - * @Date 2020/12/26 + * Default implementation of LogData. */ public class DefaultLogData implements LogData { private final Level level = Level.INFO; diff --git a/api/src/main/java/com/google/common/flogger/EventAggregator.java b/api/src/main/java/com/google/common/flogger/EventAggregator.java index 4a055100..6bb816ec 100644 --- a/api/src/main/java/com/google/common/flogger/EventAggregator.java +++ b/api/src/main/java/com/google/common/flogger/EventAggregator.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2012 The Flogger Authors. + * + * 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 com.google.common.flogger; import java.util.ArrayList; @@ -7,33 +23,77 @@ import java.util.concurrent.ScheduledExecutorService; /** - * @Desctiption - * @Author wallace - * @Date 2020/12/26 + * EventAggregator is used to log many same type events. The typical scenario is api request and response. + *

+ * If we log each api request and response, the log data will be too large. But if we do not log each api request and + * response, when there are some errors, we can not confirm if we get the request or return the response. + *

+ * EventAggregator can aggregate many same type events and log the key information within one log. + * For example: + * requestId=1053:200 | requestId=1054:200 | requestId=1055:500 | requestId=1056:200 | requestId=1057:200 | + * requestId=1063:200 | requestId=1064:200 | requestId=1065:200 | requestId=1066:404 | requestId=1067:200 | + *

+ * The best practice is to use {@link EventAggregator} to log brief information for all api request and response, + * and use {@link FluentLogger} to log detailed information for error requests and responses. + * */ public class EventAggregator extends AggregatedLogContext { + /** + * Simple Event pair. + */ static final class EventPair { private final String key; private final String value; + /** + * Instantiates a new Event pair. + * + * @param key the key + * @param value the value + */ public EventPair(String key, String value){ this.key = key; this.value = value; } + /** + * Gets key. + * + * @return the key + */ public String getKey() { return key; } + /** + * Gets value. + * + * @return the value + */ public String getValue() { return value; } } + /** + * Use LinkedBlockingQueue to store events. + *

+ * Two reasons for using LinkedBlockingQueue: + * 1. Thread-safe: many threads will use the same {@link EventAggregator} to log same type events. + * 2. Async log: logging aggregated events is a time-consuming action. It's better to use separate thread to do it. + */ protected final BlockingQueue eventList = new LinkedBlockingQueue(1024 * 1024); + /** + * Instantiates a new Event aggregator. + * + * @param name the name + * @param logger the logger (see {@link FluentAggregatedLogger}). + * @param logSite the log site (see {@link LogSite}). + * @param pool the executor service pool used to periodically log data. + */ public EventAggregator(String name, FluentAggregatedLogger logger, LogSite logSite, ScheduledExecutorService pool){ super(name, logger, logSite, pool); } @@ -43,6 +103,12 @@ protected EventAggregator self() { return this; } + /** + * Add event to {@link EventAggregator}. + * + * @param event the event + * @param content the content + */ public void add(String event, String content){ eventList.offer(new EventPair(event, content)); increaseCounter(); @@ -62,8 +128,13 @@ protected String message(){ List eventBuffer = new ArrayList(); eventList.drainTo(eventBuffer, getMetadata().findValue(Key.NUMBER_WINDOW)); + return formatMessage(eventBuffer); + } + + private String formatMessage(List eventBuffer){ int bufferSize = eventBuffer.size(); StringBuilder builder = new StringBuilder(); + builder.append(name).append("\n"); for( int i = 0; i < bufferSize; i++){ builder.append(eventBuffer.get(i).getKey()).append(":") @@ -76,8 +147,7 @@ protected String message(){ builder.append("\n"); } - builder.append("\nthread:").append(Thread.currentThread().getId()); - builder.append(", total: ").append(bufferSize); + builder.append("\ntotal: ").append(bufferSize); return builder.toString(); } diff --git a/api/src/main/java/com/google/common/flogger/FluentAggregatedLogger.java b/api/src/main/java/com/google/common/flogger/FluentAggregatedLogger.java index bec7daec..c1c01cf5 100644 --- a/api/src/main/java/com/google/common/flogger/FluentAggregatedLogger.java +++ b/api/src/main/java/com/google/common/flogger/FluentAggregatedLogger.java @@ -1,7 +1,24 @@ +/* + * Copyright (C) 2012 The Flogger Authors. + * + * 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 com.google.common.flogger; import com.google.common.flogger.backend.LoggerBackend; import com.google.common.flogger.backend.Platform; +import com.google.errorprone.annotations.CheckReturnValue; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -11,13 +28,16 @@ import static com.google.common.flogger.util.Checks.checkNotNull; /** - * @Desctiption - * @Author wallace - * @Date 2020/12/26 + * The default aggregated logger which uses the default parser and system configured backend. + *

+ * FluentAggregatedLogger will initiate {@link EventAggregator} or {@link StatAggregator}. */ -public final class FluentAggregatedLogger extends AbstractAggregatedLogger { +@CheckReturnValue +public final class FluentAggregatedLogger extends AbstractLogger { + //Executor service pool for all aggregated loggers to periodically flush log. private static final ScheduledExecutorService pool = Executors.newScheduledThreadPool(8); + //Cache the AggregatedLogContext instance for multi threads private final Map aggregatorMap = new ConcurrentHashMap(); private FluentAggregatedLogger(LoggerBackend backend) { @@ -31,6 +51,12 @@ public static FluentAggregatedLogger forEnclosingClass() { return new FluentAggregatedLogger(Platform.getBackend(loggingClass)); } + /** + * Get EventAggregator + * + * @param name aggregator logger name, should be unique in the same FluentAggregatedLogger scope. + * @return EventAggregator + */ public EventAggregator getEvent(String name ){ AggregatedLogContext aggregator = aggregatorMap.get(name); if(aggregator == null){ @@ -39,7 +65,12 @@ public EventAggregator getEvent(String name ){ "logger backend must not return a null LogSite"); aggregator = new EventAggregator(name, this, logSite, pool); aggregatorMap.putIfAbsent(name, aggregator); - aggregator = aggregatorMap.get(name); + + AggregatedLogContext old = aggregatorMap.putIfAbsent(name, aggregator); + + if( old != null ) { + aggregator = old; + } } if( !(aggregator instanceof EventAggregator)){ @@ -49,6 +80,12 @@ public EventAggregator getEvent(String name ){ return (EventAggregator)aggregator; } + /** + * Get StatAggregator + * + * @param name aggregator logger name, should be unique in the same FluentAggregatedLogger scope. + * @return StatAggregator + */ public StatAggregator getStat(String name ){ AggregatedLogContext aggregator = aggregatorMap.get(name); if(aggregator == null){ @@ -56,8 +93,11 @@ public StatAggregator getStat(String name ){ LogSite logSite = checkNotNull(Platform.getCallerFinder().findLogSite(FluentAggregatedLogger.class, 0), "logger backend must not return a null LogSite"); aggregator = new StatAggregator(name, this, logSite, pool); - aggregatorMap.putIfAbsent(name, aggregator); - aggregator = aggregatorMap.get(name); + AggregatedLogContext old = aggregatorMap.putIfAbsent(name, aggregator); + + if( old != null ) { + aggregator = old; + } } if( !(aggregator instanceof StatAggregator)){ diff --git a/api/src/main/java/com/google/common/flogger/StatAggregator.java b/api/src/main/java/com/google/common/flogger/StatAggregator.java index 6f33290e..4f06f5bd 100644 --- a/api/src/main/java/com/google/common/flogger/StatAggregator.java +++ b/api/src/main/java/com/google/common/flogger/StatAggregator.java @@ -1,18 +1,70 @@ +/* + * Copyright (C) 2012 The Flogger Authors. + * + * 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 com.google.common.flogger; +import com.google.common.flogger.util.Checks; + import java.util.ArrayList; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.atomic.AtomicInteger; /** - * @Desctiption - * @Author wallace - * @Date 2020/12/28 + * StatAggregator is used to log many same type performance data. The typical scenario is API process. + *

+ * StatAggregator can aggregate many same type performance data and simply calc the min/max/total/avg value. + * For example: + * API: get-user-api + * min:60, max:87, total:735, count:10, avg:73.5. + * [CONTEXT number_window=10 sample_rate=3 unit="ms" ] + *

+ * You can combine {@link EventAggregator} and {@link StatAggregator} and {@link FluentLogger} + * to log full information for API or other kind of event like this: + * - use {@link EventAggregator} to log key information like request id and response code. + * - use {@link StatAggregator} to log performance information + * - use {@link FluentLogger} to log detailed information for error requests or responses. + * */ public class StatAggregator extends AggregatedLogContext { + public static final class Key { + private Key() { + } + + public static final MetadataKey SAMPLE_RATE = + MetadataKey.single("sample_rate", Integer.class); + + public static final MetadataKey UNIT_STRING = + MetadataKey.single("unit", String.class); + } + + //Only calc some data + private volatile int sampleRate = 1; //Calc all data by default. + private final AtomicInteger sampleCounter = new AtomicInteger(1); + + /** + * Use LinkedBlockingQueue to store values. + *

+ * Two reasons for using LinkedBlockingQueue: + * 1. Thread-safe: many threads will use the same {@link StatAggregator} to log same type values. + * 2. Async log: logging aggregated value is a time-consuming action. It's better to use separate thread to do it. + */ protected final BlockingQueue valueList = new LinkedBlockingQueue(1024 * 1024); @@ -25,7 +77,43 @@ protected StatAggregator self() { return this; } + /** + * Set sample rate + * + * @param sampleRate + * @return + */ + public StatAggregator setSampleRate(int sampleRate){ + Checks.checkArgument(sampleRate > 0, "Sample rate should be larger than 0"); + + this.sampleRate = sampleRate; + metadata.addValue(Key.SAMPLE_RATE, sampleRate); + + return self(); + } + + /** + * Set unit for log context. + * + * @param unit + * @return + */ + public StatAggregator setUnit(String unit){ + metadata.addValue(Key.UNIT_STRING, unit); + + return self(); + } + + /** + * Add value + * + * @param value + */ public void add(long value){ + if(!sample()){ + return; + } + valueList.offer(value); increaseCounter(); @@ -42,12 +130,21 @@ protected boolean haveData() { @Override protected String message() { List valueBuffer = new ArrayList(); - valueList.drainTo(valueBuffer, getMetadata().findValue(Key.NUMBER_WINDOW)); + valueList.drainTo(valueBuffer, metadata.findValue(AggregatedLogContext.Key.NUMBER_WINDOW)); + + return formatMessage(valueBuffer); + } + + private boolean sample(){ + return sampleRate == 1 || (sampleCounter.getAndDecrement() % sampleRate == 0); + } + + private String formatMessage(List valueBuffer){ long min = Long.MAX_VALUE; long max = Long.MIN_VALUE; long total = 0; - long avg = 0; + double avg = 0; for(Long e : valueBuffer){ if(e > max){ @@ -65,7 +162,7 @@ protected String message() { builder.append(name).append("\n"); if(!valueBuffer.isEmpty()) { - avg = total / valueBuffer.size(); + avg = Double.valueOf(total) / valueBuffer.size(); String sep = ", "; builder.append("min:").append(min).append(sep) @@ -74,7 +171,7 @@ protected String message() { .append("count:").append(valueBuffer.size()).append(sep) .append("avg:").append(avg).append("."); } else { - builder.append("no data\n"); + builder.append(" "); } return builder.toString(); diff --git a/api/src/main/java/com/google/common/flogger/example/FluentAggregatedLoggerExample.java b/api/src/main/java/com/google/common/flogger/example/FluentAggregatedLoggerExample.java index 6d9c99af..3c2394cd 100644 --- a/api/src/main/java/com/google/common/flogger/example/FluentAggregatedLoggerExample.java +++ b/api/src/main/java/com/google/common/flogger/example/FluentAggregatedLoggerExample.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2012 The Flogger Authors. + * + * 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 com.google.common.flogger.example; import com.google.common.flogger.EventAggregator; @@ -8,15 +24,13 @@ import java.util.logging.Level; /** - * @Desctiption - * @Author wallace - * @Date 2020/12/26 + * FluentAggregatedLogger Example. + * You can run this example directly from Intellij IDE by choose "fluent_aggregated_logger_example" target. */ public class FluentAggregatedLoggerExample { final static FluentLogger logger1 = FluentLogger.forEnclosingClass(); final static FluentAggregatedLogger logger2 = FluentAggregatedLogger.forEnclosingClass(); - public static void main(String[] args) throws InterruptedException { logger1.at(Level.INFO).log("hello, world"); @@ -25,13 +39,10 @@ public static void main(String[] args) throws InterruptedException { eventAggregator.add("requestId=10" + i, "200"); } - StatAggregator statAggregator = logger2.getStat("get-user-api-perf").timeWindow(5).numberWindow(10); + StatAggregator statAggregator = logger2.getStat("get-user-api-perf") + .numberWindow(10).setSampleRate(3).setUnit("ms"); for(int i = 0; i < 100; i++) { statAggregator.add(i); } - - Thread.sleep(3000); - - statAggregator.add(100); } } From 04cbaa5209663a0f3dfe850be9f709ca28cf358b Mon Sep 17 00:00:00 2001 From: wallace Date: Mon, 4 Jan 2021 09:28:52 +0800 Subject: [PATCH 3/8] add trying logic when blocking queue for aggregated logger is full --- .../common/flogger/AggregatedLogContext.java | 3 +- .../common/flogger/EventAggregator.java | 32 +++++++++++++++---- .../google/common/flogger/StatAggregator.java | 30 +++++++++++++---- 3 files changed, 50 insertions(+), 15 deletions(-) diff --git a/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java b/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java index f69e36c7..766d25dc 100644 --- a/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java +++ b/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java @@ -225,8 +225,7 @@ protected LogData data() { } /** - * Flush aggregated data in new Thread if number window is full. - * Please always call shouldFlush() first. + * Flush aggregated data in new Thread. */ protected void flush(){ new Thread(new Runnable() { diff --git a/api/src/main/java/com/google/common/flogger/EventAggregator.java b/api/src/main/java/com/google/common/flogger/EventAggregator.java index 6bb816ec..2b69f5b1 100644 --- a/api/src/main/java/com/google/common/flogger/EventAggregator.java +++ b/api/src/main/java/com/google/common/flogger/EventAggregator.java @@ -21,6 +21,7 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; /** * EventAggregator is used to log many same type events. The typical scenario is api request and response. @@ -109,13 +110,30 @@ protected EventAggregator self() { * @param event the event * @param content the content */ - public void add(String event, String content){ - eventList.offer(new EventPair(event, content)); - increaseCounter(); - - if(shouldFlush()){ - flush(); - } + public void add(String event, String content) { + //try 3 times + int i = 0; + while(i++ < 3) { + try { + if(eventList.offer(new EventPair(event, content), 1, TimeUnit.MILLISECONDS)) { + increaseCounter(); + + if(shouldFlush()){ + flush(); + } + + break; + } else { + //If BlockingQueue is full, just immediately flush + flush(); + } + } catch (InterruptedException e) { + if(i == 2) { + //Do not log anything, just print stacktrace + e.printStackTrace(); + } + }; + }; } @Override diff --git a/api/src/main/java/com/google/common/flogger/StatAggregator.java b/api/src/main/java/com/google/common/flogger/StatAggregator.java index 4f06f5bd..313e780f 100644 --- a/api/src/main/java/com/google/common/flogger/StatAggregator.java +++ b/api/src/main/java/com/google/common/flogger/StatAggregator.java @@ -23,6 +23,7 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** @@ -114,12 +115,29 @@ public void add(long value){ return; } - valueList.offer(value); - increaseCounter(); - - if(shouldFlush()){ - flush(); - } + //try 3 times + int i = 0; + while(i++ < 3) { + try { + if(valueList.offer(value, 1, TimeUnit.MILLISECONDS)) { + increaseCounter(); + + if(shouldFlush()){ + flush(); + } + + break; + } else { + //If BlockingQueue is full, just immediately flush + flush(); + } + } catch (InterruptedException e) { + if(i == 2) { + //Do not log anything, just print stacktrace + e.printStackTrace(); + } + }; + }; } @Override From 6b72b05b6bb22a123e5c5c0ee94bb98af96de86d Mon Sep 17 00:00:00 2001 From: wallace Date: Tue, 5 Jan 2021 11:14:30 +0800 Subject: [PATCH 4/8] add AggregatedLogContextTest and FluentAggregatedLoggerTest --- .../common/flogger/AggregatedLogContext.java | 86 ++++++---- .../common/flogger/AggregatedLoggingApi.java | 6 +- .../google/common/flogger/DefaultLogData.java | 2 +- .../common/flogger/EventAggregator.java | 7 +- .../flogger/FluentAggregatedLogger.java | 5 +- .../google/common/flogger/StatAggregator.java | 2 +- .../FluentAggregatedLoggerExample.java | 4 +- .../flogger/AggregatedLogContextTest.java | 150 ++++++++++++++++++ .../flogger/FluentAggregatedLoggerTest.java | 98 +++++++++++- 9 files changed, 309 insertions(+), 51 deletions(-) create mode 100644 api/src/test/java/com/google/common/flogger/AggregatedLogContextTest.java diff --git a/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java b/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java index 766d25dc..138121a0 100644 --- a/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java +++ b/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Flogger Authors. + * Copyright (C) 2020 The Flogger Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,6 +65,9 @@ private final class LogFlusher implements Runnable { @Override public void run() { log(); + + LogData logData = getLogData(getName() + " periodically flush log finished"); + getLogger().write(logData); } } @@ -72,7 +75,7 @@ public void run() { private AtomicLong counter = new AtomicLong(0); //Runnable for time window - private LogFlusher flusher; + private volatile LogFlusher flusher; //Only one thread will flush log private AtomicBoolean flushLock = new AtomicBoolean(false); @@ -115,38 +118,56 @@ protected AggregatedLogContext(String name, FluentAggregatedLogger logger, LogSi this.pool = checkNotNull(pool, "pool"); } + public String getName() { + return name; + } + /** - * Schedule log flusher at fixed rate + * Schedule log flusher at fixed rate of time window. + * Please call withTimeWindow() before start() to set time window. * - * @param period */ - private void start(long period){ + public synchronized API start(){ + if(flusher != null){ + return self(); + } + + Integer period = metadata.findValue(Key.TIME_WINDOW); + if(period == null){ + throw new RuntimeException("Call withTimeWindow to set time window first"); + } flusher = new LogFlusher(); pool.scheduleAtFixedRate(flusher, 2, period, TimeUnit.SECONDS); + + return self(); } @Override - public synchronized API timeWindow(int seconds) { + public API withTimeWindow(int seconds) { if(flusher != null){ - return self(); - } + throw new RuntimeException("Please do not change time window after logger start."); + } - Checks.checkArgument(seconds > 0, "Time window should be larger than 0"); + Checks.checkArgument(seconds > 0 && seconds <= 3600, "Time window range should be (0,60]"); - metadata.addValue(Key.TIME_WINDOW, seconds); //just for logger backend to print CONTEXT - start(seconds); + metadata.addValue(Key.TIME_WINDOW, seconds); return self(); } @Override - public API numberWindow(int number) { - Checks.checkArgument(number > 0, "Number window should be larger than 0"); + public API withNumberWindow(int number) { + Checks.checkArgument(number > 0 && number <= 1000 * 1000, "Number window range should be (0, 1000000])"); metadata.addValue(Key.NUMBER_WINDOW, number); return self(); } + protected int getWindowNumber(){ + Integer numberWindows = metadata.findValue(Key.NUMBER_WINDOW); + return numberWindows == null ? 100 : numberWindows; + } + /** * Gets metadata. * @@ -170,7 +191,7 @@ protected FluentAggregatedLogger getLogger() { * Increase counter. */ protected void increaseCounter(){ - increaseCounter(0); + increaseCounter(1); } /** @@ -183,22 +204,23 @@ protected void increaseCounter(int delta){ } /** - * Check if it's time to flush data. Only for number window. + * Check if it's time to flush data. Only check for number window. * No need to check for time window because executor service pool will periodically flush data. * + * Notice: visible for test directly calling. + * * @return the boolean */ protected boolean shouldFlush() { //check number window - Long currentCounter = counter.get(); - int numberWindows = metadata.findValue(Key.NUMBER_WINDOW); + long currentCounter = counter.get(); //No need to check if currentCounter < 0 - if(currentCounter % numberWindows == 0){ - return true; + if(currentCounter == 0 || currentCounter % getWindowNumber() != 0){ + return false; } - return false; + return true; } /** @@ -206,7 +228,7 @@ protected boolean shouldFlush() { * * @return the log data */ - protected LogData data() { + protected LogData getLogData(String message) { long timestampNanos = Platform.getCurrentTimeNanos(); String loggerName = getLogger().getBackend().getLoggerName(); @@ -214,8 +236,6 @@ protected LogData data() { logData.setMetadata(getMetadata()); logData.setLogSite(logSite); - - String message = message(); logData.setTemplateContext(new TemplateContext(DefaultPrintfMessageParser.getInstance(), message)); //use empty array for avoiding null exception @@ -236,20 +256,18 @@ public void run() { }).start(); } - private void log(){ + /** + * Visible for test.Because real logging action is done in different thread, + * junit will not get the async logging data when running unit test. + * So call log method directly in junit testcase. + */ + void log(){ if(flushLock.compareAndSet(false,true)) { try { - do { - boolean have = haveData(); - - //even if there is no data to log, we still write a empty log to show timer is working. - LogData logData = data(); + while (haveData()) { + LogData logData = getLogData(message()); getLogger().write(logData); - - if(!have){ - break; - } - }while (true); + }; } finally { flushLock.compareAndSet(true, false); diff --git a/api/src/main/java/com/google/common/flogger/AggregatedLoggingApi.java b/api/src/main/java/com/google/common/flogger/AggregatedLoggingApi.java index 70c31656..c54ea095 100644 --- a/api/src/main/java/com/google/common/flogger/AggregatedLoggingApi.java +++ b/api/src/main/java/com/google/common/flogger/AggregatedLoggingApi.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Flogger Authors. + * Copyright (C) 2020 The Flogger Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,7 @@ public interface AggregatedLoggingApi { * @param seconds * @return */ - API timeWindow(int seconds); + API withTimeWindow(int seconds); /** * Set aggregated logger number window. @@ -42,5 +42,5 @@ public interface AggregatedLoggingApi { * @param number * @return */ - API numberWindow(int number); + API withNumberWindow(int number); } diff --git a/api/src/main/java/com/google/common/flogger/DefaultLogData.java b/api/src/main/java/com/google/common/flogger/DefaultLogData.java index 1868e0fe..3d286842 100644 --- a/api/src/main/java/com/google/common/flogger/DefaultLogData.java +++ b/api/src/main/java/com/google/common/flogger/DefaultLogData.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Flogger Authors. + * Copyright (C) 2020 The Flogger Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/src/main/java/com/google/common/flogger/EventAggregator.java b/api/src/main/java/com/google/common/flogger/EventAggregator.java index 2b69f5b1..382df627 100644 --- a/api/src/main/java/com/google/common/flogger/EventAggregator.java +++ b/api/src/main/java/com/google/common/flogger/EventAggregator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Flogger Authors. + * Copyright (C) 2020 The Flogger Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package com.google.common.flogger; +import com.google.errorprone.annotations.CheckReturnValue; + import java.util.ArrayList; import java.util.List; import java.util.concurrent.BlockingQueue; @@ -38,6 +40,7 @@ * and use {@link FluentLogger} to log detailed information for error requests and responses. * */ +@CheckReturnValue public class EventAggregator extends AggregatedLogContext { /** @@ -144,7 +147,7 @@ protected boolean haveData() { @Override protected String message(){ List eventBuffer = new ArrayList(); - eventList.drainTo(eventBuffer, getMetadata().findValue(Key.NUMBER_WINDOW)); + eventList.drainTo(eventBuffer, getWindowNumber()); return formatMessage(eventBuffer); } diff --git a/api/src/main/java/com/google/common/flogger/FluentAggregatedLogger.java b/api/src/main/java/com/google/common/flogger/FluentAggregatedLogger.java index c1c01cf5..7ff4b2b8 100644 --- a/api/src/main/java/com/google/common/flogger/FluentAggregatedLogger.java +++ b/api/src/main/java/com/google/common/flogger/FluentAggregatedLogger.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Flogger Authors. + * Copyright (C) 2020 The Flogger Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,7 +40,8 @@ public final class FluentAggregatedLogger extends AbstractLogger { //Cache the AggregatedLogContext instance for multi threads private final Map aggregatorMap = new ConcurrentHashMap(); - private FluentAggregatedLogger(LoggerBackend backend) { + //Visible for unit testing + protected FluentAggregatedLogger(LoggerBackend backend) { super(backend); } diff --git a/api/src/main/java/com/google/common/flogger/StatAggregator.java b/api/src/main/java/com/google/common/flogger/StatAggregator.java index 313e780f..e71e73bd 100644 --- a/api/src/main/java/com/google/common/flogger/StatAggregator.java +++ b/api/src/main/java/com/google/common/flogger/StatAggregator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Flogger Authors. + * Copyright (C) 2020 The Flogger Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/src/main/java/com/google/common/flogger/example/FluentAggregatedLoggerExample.java b/api/src/main/java/com/google/common/flogger/example/FluentAggregatedLoggerExample.java index 3c2394cd..e01919fc 100644 --- a/api/src/main/java/com/google/common/flogger/example/FluentAggregatedLoggerExample.java +++ b/api/src/main/java/com/google/common/flogger/example/FluentAggregatedLoggerExample.java @@ -34,13 +34,13 @@ public class FluentAggregatedLoggerExample { public static void main(String[] args) throws InterruptedException { logger1.at(Level.INFO).log("hello, world"); - EventAggregator eventAggregator = logger2.getEvent("get-user-api-resp").timeWindow(5).numberWindow(20); + EventAggregator eventAggregator = logger2.getEvent("get-user-api-resp").withTimeWindow(5).withNumberWindow(20).start(); for(int i = 0; i < 92; i++) { eventAggregator.add("requestId=10" + i, "200"); } StatAggregator statAggregator = logger2.getStat("get-user-api-perf") - .numberWindow(10).setSampleRate(3).setUnit("ms"); + .withNumberWindow(10).setSampleRate(3).setUnit("ms"); for(int i = 0; i < 100; i++) { statAggregator.add(i); } diff --git a/api/src/test/java/com/google/common/flogger/AggregatedLogContextTest.java b/api/src/test/java/com/google/common/flogger/AggregatedLogContextTest.java new file mode 100644 index 00000000..fac10ebd --- /dev/null +++ b/api/src/test/java/com/google/common/flogger/AggregatedLogContextTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2020 The Flogger Authors. + * + * 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 com.google.common.flogger; + +import com.google.common.flogger.testing.FakeLoggerBackend; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static org.junit.Assert.fail; + +@RunWith(JUnit4.class) +public class AggregatedLogContextTest { + final static FluentAggregatedLogger logger2 = FluentAggregatedLogger.forEnclosingClass(); + + @Test + public void testGetName(){ + String name = "test"; + FakeLoggerBackend backend = new FakeLoggerBackend(); + FluentAggregatedLogger logger = new FluentAggregatedLogger(backend); + EventAggregator eventAggregator = logger.getEvent(name); + + Assert.assertEquals(eventAggregator.getName(), name); + } + + @Test + public void testWithTimeWindow() { + FakeLoggerBackend backend = new FakeLoggerBackend(); + FluentAggregatedLogger logger = new FluentAggregatedLogger(backend); + EventAggregator eventAggregator = logger.getEvent("test"); + + // Test lower bound + try { + eventAggregator.withTimeWindow(-1); + fail("expected IllegalArgumentException"); + } catch (IllegalArgumentException e){ + } + + try { + eventAggregator.withTimeWindow(0); + fail("expected IllegalArgumentException"); + } catch (IllegalArgumentException e){ + } + + eventAggregator.withTimeWindow(1).add("timewindow","1"); + eventAggregator.log(); + backend.assertLogged(0).metadata().containsUniqueEntry(AggregatedLogContext.Key.TIME_WINDOW, 1); + + // Test upper bound + try { + eventAggregator.withTimeWindow(3601); + fail("expected IllegalArgumentException"); + } catch (IllegalArgumentException e){ + + } + try { + eventAggregator.withTimeWindow(10000); + fail("expected IllegalArgumentException"); + } catch (IllegalArgumentException e){ + e.printStackTrace(); + } + + EventAggregator eventAggregator2 = logger.getEvent("test2"); + eventAggregator2.withTimeWindow(3600).add("timewindow","3600"); + eventAggregator2.log(); + backend.assertLogged(1).metadata().containsUniqueEntry(AggregatedLogContext.Key.TIME_WINDOW, 3600); + + // Test repeatedly set + eventAggregator.withTimeWindow(2).add("timewindow","2"); + eventAggregator.log(); + backend.assertLogged(2).metadata().containsUniqueEntry(AggregatedLogContext.Key.TIME_WINDOW, 2); + try { + eventAggregator = eventAggregator.start().withTimeWindow(3); + fail("expected RuntimeException"); + } catch (RuntimeException e){ + } + } + + @Test + public void testWithNumberWindow() { + FakeLoggerBackend backend = new FakeLoggerBackend(); + FluentAggregatedLogger logger = new FluentAggregatedLogger(backend); + EventAggregator eventAggregator = logger.getEvent("test"); + + // Test lower bound + try { + eventAggregator.withNumberWindow(-1); + fail("expected IllegalArgumentException"); + } catch (IllegalArgumentException e){ + } + + try { + eventAggregator.withNumberWindow(0); + fail("expected IllegalArgumentException"); + } catch (IllegalArgumentException e){ + } + + eventAggregator.withNumberWindow(1).add("numberwindow","1"); + eventAggregator.log(); + backend.assertLogged(0).metadata().containsUniqueEntry(AggregatedLogContext.Key.NUMBER_WINDOW, 1); + + // Test upper bound + try { + eventAggregator.withNumberWindow(1000 * 1000 + 1); + fail("expected IllegalArgumentException"); + } catch (IllegalArgumentException e){ + + } + try { + eventAggregator.withNumberWindow(1024 * 1024); + fail("expected IllegalArgumentException"); + } catch (IllegalArgumentException e){ + e.printStackTrace(); + } + eventAggregator.withNumberWindow(1000 * 1000).add("timewindow","1000 * 1000"); + eventAggregator.log(); + backend.assertLogged(1).metadata().containsUniqueEntry(AggregatedLogContext.Key.NUMBER_WINDOW, 1000 * 1000); + } + + @Test + public void testShouldFlush(){ + FakeLoggerBackend backend = new FakeLoggerBackend(); + FluentAggregatedLogger logger = new FluentAggregatedLogger(backend); + EventAggregator eventAggregator = logger.getEvent("test"); + + eventAggregator = eventAggregator.withNumberWindow(10); + Assert.assertFalse(eventAggregator.shouldFlush()); + eventAggregator.increaseCounter(99); + Assert.assertFalse(eventAggregator.shouldFlush()); + eventAggregator.increaseCounter(); + Assert.assertTrue(eventAggregator.shouldFlush()); + eventAggregator.increaseCounter(); + Assert.assertFalse(eventAggregator.shouldFlush()); + } +} diff --git a/api/src/test/java/com/google/common/flogger/FluentAggregatedLoggerTest.java b/api/src/test/java/com/google/common/flogger/FluentAggregatedLoggerTest.java index 71050e7d..2918f9c0 100644 --- a/api/src/test/java/com/google/common/flogger/FluentAggregatedLoggerTest.java +++ b/api/src/test/java/com/google/common/flogger/FluentAggregatedLoggerTest.java @@ -1,18 +1,104 @@ +/* + * Copyright (C) 2021 The Flogger Authors. + * + * 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 com.google.common.flogger; +import com.google.common.flogger.testing.FakeLoggerBackend; +import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.util.logging.Level; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; + /** - * @Desctiption - * @Author wallace - * @Date 2020/12/26 + * FluentAggregatedLogger is typically very simple classes whose only real responsibility is as a factory + * for a specific API implementation. As such it needs very few tests itself. + * + * See AggregatedLogContextTest.java for the vast majority of tests related to base logging behaviour. */ @RunWith(JUnit4.class) public class FluentAggregatedLoggerTest { - @Test - public void testRun(){ + @Test + public void testCreate() { + FluentAggregatedLogger logger = FluentAggregatedLogger.forEnclosingClass(); + assertThat(logger.getName()).isEqualTo(FluentAggregatedLoggerTest.class.getName()); + assertThat(logger.getBackend().getLoggerName()).isEqualTo(FluentAggregatedLoggerTest.class.getName()); + } + + @Test + public void testGetEvent() { + FakeLoggerBackend backend = new FakeLoggerBackend(); + FluentAggregatedLogger logger = new FluentAggregatedLogger(backend); + backend.setLevel(Level.INFO); + + // Check name + String name1 = "event1"; + EventAggregator eventAggregator = logger.getEvent(name1); + Assert.assertEquals(eventAggregator.getName(), name1); + + // Test repeatedly get + EventAggregator same = logger.getEvent(name1); + Assert.assertEquals(eventAggregator, same); + + //Test different name + String name2 = "event2"; + EventAggregator eventAggregator2 = logger.getEvent(name2); + Assert.assertEquals(eventAggregator2.getName(), name2); + Assert.assertNotEquals(eventAggregator, eventAggregator2); + + //Test different type aggregator with the same name + try { + StatAggregator statAggregator = logger.getStat(name1); + fail("expected RuntimeException"); + } catch (RuntimeException e){ + + } + } + + @Test + public void testGetStat() { + FakeLoggerBackend backend = new FakeLoggerBackend(); + FluentAggregatedLogger logger = new FluentAggregatedLogger(backend); + backend.setLevel(Level.INFO); + + // Check name + String name1 = "stat1"; + StatAggregator statAggregator1 = logger.getStat(name1); + Assert.assertEquals(statAggregator1.getName(), name1); + + // Test repeatedly get + StatAggregator same = logger.getStat(name1); + Assert.assertEquals(statAggregator1, same); + + //Test different name + String name2 = "stat2"; + StatAggregator statAggregator2 = logger.getStat(name2); + Assert.assertEquals(statAggregator2.getName(), name2); + Assert.assertNotEquals(statAggregator1, statAggregator2); + + //Test different type aggregator with the same name + try { + EventAggregator eventAggregator = logger.getEvent(name1); + fail("expected RuntimeException"); + } catch (RuntimeException e){ - } + } + } } From 34a371b24dab89fab4b4f8cc19671f35ef8a4cf4 Mon Sep 17 00:00:00 2001 From: wallace Date: Thu, 7 Jan 2021 16:44:22 +0800 Subject: [PATCH 5/8] add StatAggregatorTest and EventAggregatorTest --- .../common/flogger/AggregatedLogContext.java | 120 ++++++------- .../common/flogger/AggregatedLoggingApi.java | 4 + .../common/flogger/EventAggregator.java | 37 ++-- .../flogger/FluentAggregatedLogger.java | 4 +- .../google/common/flogger/StatAggregator.java | 62 +++++-- .../FluentAggregatedLoggerExample.java | 2 +- .../flogger/AggregatedLogContextTest.java | 35 ++-- .../common/flogger/EventAggregatorTest.java | 91 ++++++++++ .../flogger/FluentAggregatedLoggerTest.java | 18 +- .../common/flogger/StatAggregatorTest.java | 169 ++++++++++++++++++ 10 files changed, 419 insertions(+), 123 deletions(-) create mode 100644 api/src/test/java/com/google/common/flogger/EventAggregatorTest.java create mode 100644 api/src/test/java/com/google/common/flogger/StatAggregatorTest.java diff --git a/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java b/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java index 138121a0..1205fe29 100644 --- a/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java +++ b/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java @@ -16,8 +16,6 @@ package com.google.common.flogger; -import static com.google.common.flogger.util.Checks.*; - import com.google.common.flogger.backend.LogData; import com.google.common.flogger.backend.Metadata; import com.google.common.flogger.backend.Platform; @@ -26,9 +24,11 @@ import com.google.common.flogger.util.Checks; import com.google.errorprone.annotations.CheckReturnValue; -import java.util.concurrent.*; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; + +import static com.google.common.flogger.util.Checks.checkNotNull; /** * Base class for the aggregated logger API. @@ -64,16 +64,14 @@ private Key() {} private final class LogFlusher implements Runnable { @Override public void run() { - log(); + flush(0); // Always flush all data + // Write one more log to show timer is running when there is no any data. LogData logData = getLogData(getName() + " periodically flush log finished"); getLogger().write(logData); } } - //Counter for number window - private AtomicLong counter = new AtomicLong(0); - //Runnable for time window private volatile LogFlusher flusher; @@ -89,19 +87,33 @@ public void run() { protected abstract API self(); + /** + * Check if there are enough data to log based on number window configuration. + * + * @return true: flush now; false: not flush. + */ + protected abstract boolean shouldFlushByNumber(); + /** * Check if there are some data to log. * - * @return the boolean + * @return the amount of data */ - protected abstract boolean haveData(); + protected abstract int haveData(); /** - * Format aggregated data to string for logging. + * * * @return the string */ - protected abstract String message(); + /** + * Format aggregated data to string for logging. + * + * @param count the amount of data, 0: all, >0: specified amount + * + * @return formatted string content for LogData + */ + protected abstract String message(int count); /** * Instantiates a new AggregatedLogContext. @@ -132,10 +144,7 @@ public synchronized API start(){ return self(); } - Integer period = metadata.findValue(Key.TIME_WINDOW); - if(period == null){ - throw new RuntimeException("Call withTimeWindow to set time window first"); - } + int period = getTimeWindow(); flusher = new LogFlusher(); pool.scheduleAtFixedRate(flusher, 2, period, TimeUnit.SECONDS); @@ -148,7 +157,8 @@ public API withTimeWindow(int seconds) { throw new RuntimeException("Please do not change time window after logger start."); } - Checks.checkArgument(seconds > 0 && seconds <= 3600, "Time window range should be (0,60]"); + Checks.checkArgument(seconds > 0 && seconds <= 3600, + "Time window range should be (0,3600]"); metadata.addValue(Key.TIME_WINDOW, seconds); @@ -157,15 +167,31 @@ public API withTimeWindow(int seconds) { @Override public API withNumberWindow(int number) { - Checks.checkArgument(number > 0 && number <= 1000 * 1000, "Number window range should be (0, 1000000])"); + Checks.checkArgument(number > 0 && number <= 1000 * 1000, + "Number window range should be (0, 1000000])"); metadata.addValue(Key.NUMBER_WINDOW, number); return self(); } - protected int getWindowNumber(){ - Integer numberWindows = metadata.findValue(Key.NUMBER_WINDOW); - return numberWindows == null ? 100 : numberWindows; + /** + * Get time window configuration. Default value is 60(seconds). + * + */ + @Override + public int getTimeWindow() { + Integer timeWindow = metadata.findValue(Key.TIME_WINDOW); + return timeWindow == null ? 60 : timeWindow; // Default 60 seconds + } + + /** + * Get number window configuration. Default value is 100. + * + */ + @Override + public int getNumberWindow() { + Integer numberWindow = metadata.findValue(Key.NUMBER_WINDOW); + return numberWindow == null ? 100 : numberWindow; // Default 100 } /** @@ -187,42 +213,6 @@ protected FluentAggregatedLogger getLogger() { return logger; } - /** - * Increase counter. - */ - protected void increaseCounter(){ - increaseCounter(1); - } - - /** - * Increase counter. - * - * @param delta the delta - */ - protected void increaseCounter(int delta){ - counter.addAndGet(delta); - } - - /** - * Check if it's time to flush data. Only check for number window. - * No need to check for time window because executor service pool will periodically flush data. - * - * Notice: visible for test directly calling. - * - * @return the boolean - */ - protected boolean shouldFlush() { - //check number window - long currentCounter = counter.get(); - - //No need to check if currentCounter < 0 - if(currentCounter == 0 || currentCounter % getWindowNumber() != 0){ - return false; - } - - return true; - } - /** * Generate {@link LogData} for logger backend. * @@ -247,11 +237,11 @@ protected LogData getLogData(String message) { /** * Flush aggregated data in new Thread. */ - protected void flush(){ + protected void asyncFlush(final int count){ new Thread(new Runnable() { @Override public void run() { - log(); + flush(count); } }).start(); } @@ -261,13 +251,15 @@ public void run() { * junit will not get the async logging data when running unit test. * So call log method directly in junit testcase. */ - void log(){ + protected void flush(int count){ if(flushLock.compareAndSet(false,true)) { try { - while (haveData()) { - LogData logData = getLogData(message()); - getLogger().write(logData); - }; + if(count > 0 && haveData() < count){ + return; + } + + LogData logData = getLogData(message(count)); + getLogger().write(logData); } finally { flushLock.compareAndSet(true, false); diff --git a/api/src/main/java/com/google/common/flogger/AggregatedLoggingApi.java b/api/src/main/java/com/google/common/flogger/AggregatedLoggingApi.java index c54ea095..f029bc66 100644 --- a/api/src/main/java/com/google/common/flogger/AggregatedLoggingApi.java +++ b/api/src/main/java/com/google/common/flogger/AggregatedLoggingApi.java @@ -33,6 +33,8 @@ public interface AggregatedLoggingApi { */ API withTimeWindow(int seconds); + int getTimeWindow(); + /** * Set aggregated logger number window. * @@ -43,4 +45,6 @@ public interface AggregatedLoggingApi { * @return */ API withNumberWindow(int number); + + int getNumberWindow(); } diff --git a/api/src/main/java/com/google/common/flogger/EventAggregator.java b/api/src/main/java/com/google/common/flogger/EventAggregator.java index 382df627..f955459a 100644 --- a/api/src/main/java/com/google/common/flogger/EventAggregator.java +++ b/api/src/main/java/com/google/common/flogger/EventAggregator.java @@ -16,6 +16,7 @@ package com.google.common.flogger; +import com.google.common.flogger.util.Checks; import com.google.errorprone.annotations.CheckReturnValue; import java.util.ArrayList; @@ -87,9 +88,7 @@ public String getValue() { * 1. Thread-safe: many threads will use the same {@link EventAggregator} to log same type events. * 2. Async log: logging aggregated events is a time-consuming action. It's better to use separate thread to do it. */ - protected final BlockingQueue eventList = - new LinkedBlockingQueue(1024 * 1024); - + protected final BlockingQueue eventList; /** * Instantiates a new Event aggregator. * @@ -98,8 +97,10 @@ public String getValue() { * @param logSite the log site (see {@link LogSite}). * @param pool the executor service pool used to periodically log data. */ - public EventAggregator(String name, FluentAggregatedLogger logger, LogSite logSite, ScheduledExecutorService pool){ + EventAggregator(String name, FluentAggregatedLogger logger, LogSite logSite, + ScheduledExecutorService pool, int capacity){ super(name, logger, logSite, pool); + eventList = new LinkedBlockingQueue(capacity); } @Override @@ -119,16 +120,15 @@ public void add(String event, String content) { while(i++ < 3) { try { if(eventList.offer(new EventPair(event, content), 1, TimeUnit.MILLISECONDS)) { - increaseCounter(); - - if(shouldFlush()){ - flush(); + if(shouldFlushByNumber()){ + asyncFlush(getNumberWindow()); } break; } else { //If BlockingQueue is full, just immediately flush - flush(); + asyncFlush(0); + Thread.sleep(1); } } catch (InterruptedException e) { if(i == 2) { @@ -140,14 +140,25 @@ public void add(String event, String content) { } @Override - protected boolean haveData() { - return !eventList.isEmpty(); + protected boolean shouldFlushByNumber() { + return eventList.size() >= getNumberWindow(); } @Override - protected String message(){ + protected int haveData() { + return eventList.size(); + } + + @Override + protected String message(int count){ + Checks.checkArgument(count >= 0, "count should be >=0"); + List eventBuffer = new ArrayList(); - eventList.drainTo(eventBuffer, getWindowNumber()); + if(count == 0){ + eventList.drainTo(eventBuffer); + } else { + eventList.drainTo(eventBuffer, count); + } return formatMessage(eventBuffer); } diff --git a/api/src/main/java/com/google/common/flogger/FluentAggregatedLogger.java b/api/src/main/java/com/google/common/flogger/FluentAggregatedLogger.java index 7ff4b2b8..ea5e8e12 100644 --- a/api/src/main/java/com/google/common/flogger/FluentAggregatedLogger.java +++ b/api/src/main/java/com/google/common/flogger/FluentAggregatedLogger.java @@ -64,7 +64,7 @@ public EventAggregator getEvent(String name ){ //Get logsite here for async write data in new Thread LogSite logSite = checkNotNull(Platform.getCallerFinder().findLogSite(FluentAggregatedLogger.class, 0), "logger backend must not return a null LogSite"); - aggregator = new EventAggregator(name, this, logSite, pool); + aggregator = new EventAggregator(name, this, logSite, pool, 1024 * 1024); aggregatorMap.putIfAbsent(name, aggregator); AggregatedLogContext old = aggregatorMap.putIfAbsent(name, aggregator); @@ -93,7 +93,7 @@ public StatAggregator getStat(String name ){ //Get logsite here for async write data in new Thread LogSite logSite = checkNotNull(Platform.getCallerFinder().findLogSite(FluentAggregatedLogger.class, 0), "logger backend must not return a null LogSite"); - aggregator = new StatAggregator(name, this, logSite, pool); + aggregator = new StatAggregator(name, this, logSite, pool, 1024 * 1024); AggregatedLogContext old = aggregatorMap.putIfAbsent(name, aggregator); if( old != null ) { diff --git a/api/src/main/java/com/google/common/flogger/StatAggregator.java b/api/src/main/java/com/google/common/flogger/StatAggregator.java index e71e73bd..1507daf4 100644 --- a/api/src/main/java/com/google/common/flogger/StatAggregator.java +++ b/api/src/main/java/com/google/common/flogger/StatAggregator.java @@ -66,11 +66,12 @@ private Key() { * 1. Thread-safe: many threads will use the same {@link StatAggregator} to log same type values. * 2. Async log: logging aggregated value is a time-consuming action. It's better to use separate thread to do it. */ - protected final BlockingQueue valueList = - new LinkedBlockingQueue(1024 * 1024); + protected final BlockingQueue valueList; - protected StatAggregator(String name, FluentAggregatedLogger logger, LogSite logSite, ScheduledExecutorService pool) { + protected StatAggregator(String name, FluentAggregatedLogger logger, LogSite logSite, + ScheduledExecutorService pool, int capacity) { super(name, logger, logSite, pool); + valueList = new LinkedBlockingQueue(capacity); } @Override @@ -84,27 +85,37 @@ protected StatAggregator self() { * @param sampleRate * @return */ - public StatAggregator setSampleRate(int sampleRate){ - Checks.checkArgument(sampleRate > 0, "Sample rate should be larger than 0"); + public StatAggregator withSampleRate(int sampleRate){ + Checks.checkArgument(sampleRate > 0 && sampleRate <= 1000, + "Sample rate range should be (0,1000]"); this.sampleRate = sampleRate; - metadata.addValue(Key.SAMPLE_RATE, sampleRate); + metadata.addValue(Key.SAMPLE_RATE, sampleRate); //Just for log context return self(); } + public int getSampleRate(){ + return sampleRate; + } + /** * Set unit for log context. * * @param unit * @return */ - public StatAggregator setUnit(String unit){ + public StatAggregator withUnit(String unit){ metadata.addValue(Key.UNIT_STRING, unit); return self(); } + public String getUnit(){ + String unit = metadata.findValue(Key.UNIT_STRING); + return unit; + } + /** * Add value * @@ -120,16 +131,15 @@ public void add(long value){ while(i++ < 3) { try { if(valueList.offer(value, 1, TimeUnit.MILLISECONDS)) { - increaseCounter(); - - if(shouldFlush()){ - flush(); + if(shouldFlushByNumber()){ + asyncFlush(getNumberWindow()); } break; } else { //If BlockingQueue is full, just immediately flush - flush(); + asyncFlush(0); + Thread.sleep(1); } } catch (InterruptedException e) { if(i == 2) { @@ -141,20 +151,36 @@ public void add(long value){ } @Override - protected boolean haveData() { - return !valueList.isEmpty(); + protected boolean shouldFlushByNumber() { + return valueList.size() >= getNumberWindow(); + } + + @Override + protected int haveData() { + return valueList.size(); } @Override - protected String message() { + protected String message(int count) { + Checks.checkArgument(count >= 0, "count should be larger than 0"); + List valueBuffer = new ArrayList(); - valueList.drainTo(valueBuffer, metadata.findValue(AggregatedLogContext.Key.NUMBER_WINDOW)); + if(count == 0){ + valueList.drainTo(valueBuffer); + } else { + valueList.drainTo(valueBuffer, count); + } return formatMessage(valueBuffer); } - private boolean sample(){ - return sampleRate == 1 || (sampleCounter.getAndDecrement() % sampleRate == 0); + /** + * Sample data + * + * @return true: log; false: skip + */ + protected boolean sample(){ + return sampleRate == 1 || (sampleCounter.getAndIncrement() % sampleRate == 0); } private String formatMessage(List valueBuffer){ diff --git a/api/src/main/java/com/google/common/flogger/example/FluentAggregatedLoggerExample.java b/api/src/main/java/com/google/common/flogger/example/FluentAggregatedLoggerExample.java index e01919fc..d8287bc9 100644 --- a/api/src/main/java/com/google/common/flogger/example/FluentAggregatedLoggerExample.java +++ b/api/src/main/java/com/google/common/flogger/example/FluentAggregatedLoggerExample.java @@ -40,7 +40,7 @@ public static void main(String[] args) throws InterruptedException { } StatAggregator statAggregator = logger2.getStat("get-user-api-perf") - .withNumberWindow(10).setSampleRate(3).setUnit("ms"); + .withNumberWindow(10).withSampleRate(3).withUnit("ms"); for(int i = 0; i < 100; i++) { statAggregator.add(i); } diff --git a/api/src/test/java/com/google/common/flogger/AggregatedLogContextTest.java b/api/src/test/java/com/google/common/flogger/AggregatedLogContextTest.java index fac10ebd..5c059925 100644 --- a/api/src/test/java/com/google/common/flogger/AggregatedLogContextTest.java +++ b/api/src/test/java/com/google/common/flogger/AggregatedLogContextTest.java @@ -17,11 +17,11 @@ package com.google.common.flogger; import com.google.common.flogger.testing.FakeLoggerBackend; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; @RunWith(JUnit4.class) @@ -35,7 +35,7 @@ public void testGetName(){ FluentAggregatedLogger logger = new FluentAggregatedLogger(backend); EventAggregator eventAggregator = logger.getEvent(name); - Assert.assertEquals(eventAggregator.getName(), name); + assertThat(eventAggregator.getName()).isEqualTo(name); } @Test @@ -58,7 +58,7 @@ public void testWithTimeWindow() { } eventAggregator.withTimeWindow(1).add("timewindow","1"); - eventAggregator.log(); + eventAggregator.flush(0); backend.assertLogged(0).metadata().containsUniqueEntry(AggregatedLogContext.Key.TIME_WINDOW, 1); // Test upper bound @@ -77,12 +77,12 @@ public void testWithTimeWindow() { EventAggregator eventAggregator2 = logger.getEvent("test2"); eventAggregator2.withTimeWindow(3600).add("timewindow","3600"); - eventAggregator2.log(); + eventAggregator2.flush(0); backend.assertLogged(1).metadata().containsUniqueEntry(AggregatedLogContext.Key.TIME_WINDOW, 3600); // Test repeatedly set eventAggregator.withTimeWindow(2).add("timewindow","2"); - eventAggregator.log(); + eventAggregator.flush(0); backend.assertLogged(2).metadata().containsUniqueEntry(AggregatedLogContext.Key.TIME_WINDOW, 2); try { eventAggregator = eventAggregator.start().withTimeWindow(3); @@ -111,7 +111,7 @@ public void testWithNumberWindow() { } eventAggregator.withNumberWindow(1).add("numberwindow","1"); - eventAggregator.log(); + eventAggregator.flush(0); backend.assertLogged(0).metadata().containsUniqueEntry(AggregatedLogContext.Key.NUMBER_WINDOW, 1); // Test upper bound @@ -128,23 +128,28 @@ public void testWithNumberWindow() { e.printStackTrace(); } eventAggregator.withNumberWindow(1000 * 1000).add("timewindow","1000 * 1000"); - eventAggregator.log(); + eventAggregator.flush(0); backend.assertLogged(1).metadata().containsUniqueEntry(AggregatedLogContext.Key.NUMBER_WINDOW, 1000 * 1000); } @Test - public void testShouldFlush(){ + public void testShouldFlush() throws InterruptedException { FakeLoggerBackend backend = new FakeLoggerBackend(); FluentAggregatedLogger logger = new FluentAggregatedLogger(backend); EventAggregator eventAggregator = logger.getEvent("test"); + assertThat(eventAggregator.shouldFlushByNumber()).isFalse(); + eventAggregator = eventAggregator.withNumberWindow(10); - Assert.assertFalse(eventAggregator.shouldFlush()); - eventAggregator.increaseCounter(99); - Assert.assertFalse(eventAggregator.shouldFlush()); - eventAggregator.increaseCounter(); - Assert.assertTrue(eventAggregator.shouldFlush()); - eventAggregator.increaseCounter(); - Assert.assertFalse(eventAggregator.shouldFlush()); + for(int i = 0; i < 9; i++){ + eventAggregator.add("hello", "world"); + } + assertThat(eventAggregator.shouldFlushByNumber()).isFalse(); + eventAggregator.add("hello", "world"); + assertThat(eventAggregator.shouldFlushByNumber()).isTrue(); + + Thread.sleep(10); // Waiting for async flush thread to finish + eventAggregator.add("hello", "world"); + assertThat(eventAggregator.shouldFlushByNumber()).isFalse(); } } diff --git a/api/src/test/java/com/google/common/flogger/EventAggregatorTest.java b/api/src/test/java/com/google/common/flogger/EventAggregatorTest.java new file mode 100644 index 00000000..84afeaa2 --- /dev/null +++ b/api/src/test/java/com/google/common/flogger/EventAggregatorTest.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2021 The Flogger Authors. + * + * 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 com.google.common.flogger; + +import com.google.common.flogger.backend.Platform; +import com.google.common.flogger.testing.FakeLoggerBackend; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +import static com.google.common.flogger.util.Checks.checkNotNull; +import static com.google.common.truth.Truth.assertThat; + +@RunWith(JUnit4.class) +public class EventAggregatorTest { + + private EventAggregator create(String name, int capacity, FakeLoggerBackend backend){ + FluentAggregatedLogger logger = new FluentAggregatedLogger(backend); + LogSite logSite = checkNotNull(Platform.getCallerFinder().findLogSite(FluentAggregatedLogger.class, 0), + "logger backend must not return a null LogSite"); + ScheduledExecutorService pool = Executors.newScheduledThreadPool(1); + + return new EventAggregator(name, logger, logSite, pool, capacity); + } + + @Test + public void testAdd() throws InterruptedException { + String name = "test"; + FakeLoggerBackend backend = new FakeLoggerBackend(); + EventAggregator eventAggregator = create(name, 2, backend); + + assertThat(eventAggregator.getName()).isEqualTo(name); + + eventAggregator.add("event1","hello"); + eventAggregator.add("event2","world"); + eventAggregator.add("event3","flogger"); + + Thread.sleep(3); //wait for async flush thread to finish + eventAggregator.flush(0); + + String message1 = "test\n" + + "event1:hello | event2:world | \n" + + "\n" + + "total: 2"; + backend.assertLogged(0).hasMessage(message1); + + String message2 = "test\n" + + "event3:flogger | \n" + + "\n" + + "total: 1"; + backend.assertLogged(1).hasMessage(message2); + } + + @Test + public void testHaveData() { + String name = "test"; + FakeLoggerBackend backend = new FakeLoggerBackend(); + EventAggregator eventAggregator = create(name, 2, backend); + + eventAggregator.add("event1","hello"); + assertThat(eventAggregator.haveData()).isEqualTo(1); + + eventAggregator.add("event2","world"); + assertThat(eventAggregator.haveData()).isEqualTo(2); + + eventAggregator.add("event3","flogger"); + assertThat(eventAggregator.haveData()).isEqualTo(1); + //Assert.assertEquals(eventAggregator.haveData(),3); + + eventAggregator.flush(0); + assertThat(eventAggregator.haveData()).isEqualTo(0); + + } +} diff --git a/api/src/test/java/com/google/common/flogger/FluentAggregatedLoggerTest.java b/api/src/test/java/com/google/common/flogger/FluentAggregatedLoggerTest.java index 2918f9c0..e3c94084 100644 --- a/api/src/test/java/com/google/common/flogger/FluentAggregatedLoggerTest.java +++ b/api/src/test/java/com/google/common/flogger/FluentAggregatedLoggerTest.java @@ -17,7 +17,6 @@ package com.google.common.flogger; import com.google.common.flogger.testing.FakeLoggerBackend; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -51,17 +50,16 @@ public void testGetEvent() { // Check name String name1 = "event1"; EventAggregator eventAggregator = logger.getEvent(name1); - Assert.assertEquals(eventAggregator.getName(), name1); - + assertThat(eventAggregator.getName()).isEqualTo(name1); // Test repeatedly get EventAggregator same = logger.getEvent(name1); - Assert.assertEquals(eventAggregator, same); + assertThat(eventAggregator).isSameInstanceAs(same); //Test different name String name2 = "event2"; EventAggregator eventAggregator2 = logger.getEvent(name2); - Assert.assertEquals(eventAggregator2.getName(), name2); - Assert.assertNotEquals(eventAggregator, eventAggregator2); + assertThat(eventAggregator2.getName()).isEqualTo(name2); + assertThat(eventAggregator).isNotSameInstanceAs(eventAggregator2); //Test different type aggregator with the same name try { @@ -81,17 +79,17 @@ public void testGetStat() { // Check name String name1 = "stat1"; StatAggregator statAggregator1 = logger.getStat(name1); - Assert.assertEquals(statAggregator1.getName(), name1); + assertThat(statAggregator1.getName()).isEqualTo(name1); // Test repeatedly get StatAggregator same = logger.getStat(name1); - Assert.assertEquals(statAggregator1, same); + assertThat(statAggregator1).isEqualTo(same); //Test different name String name2 = "stat2"; StatAggregator statAggregator2 = logger.getStat(name2); - Assert.assertEquals(statAggregator2.getName(), name2); - Assert.assertNotEquals(statAggregator1, statAggregator2); + assertThat(statAggregator2.getName()).isEqualTo(name2); + assertThat(statAggregator1).isNotSameInstanceAs(statAggregator2); //Test different type aggregator with the same name try { diff --git a/api/src/test/java/com/google/common/flogger/StatAggregatorTest.java b/api/src/test/java/com/google/common/flogger/StatAggregatorTest.java new file mode 100644 index 00000000..a25200d3 --- /dev/null +++ b/api/src/test/java/com/google/common/flogger/StatAggregatorTest.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2020 The Flogger Authors. + * + * 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 com.google.common.flogger; + +import com.google.common.flogger.backend.Platform; +import com.google.common.flogger.testing.FakeLoggerBackend; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +import static com.google.common.flogger.util.Checks.checkNotNull; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; + +@RunWith(JUnit4.class) +public class StatAggregatorTest { + + private StatAggregator create(String name, int capacity, FakeLoggerBackend backend){ + FluentAggregatedLogger logger = new FluentAggregatedLogger(backend); + LogSite logSite = checkNotNull(Platform.getCallerFinder().findLogSite(FluentAggregatedLogger.class, 0), + "logger backend must not return a null LogSite"); + ScheduledExecutorService pool = Executors.newScheduledThreadPool(1); + + return new StatAggregator(name, logger, logSite, pool, capacity); + } + + @Test + public void testWithSampleRate() { + FakeLoggerBackend backend = new FakeLoggerBackend(); + StatAggregator statAggregator = create("test", 3, backend); + + // Test lower bound + try { + statAggregator = statAggregator.withSampleRate(-1); + fail("expected IllegalArgumentException"); + } catch (IllegalArgumentException e){ + } + + try { + statAggregator = statAggregator.withSampleRate(0); + fail("expected IllegalArgumentException"); + } catch (IllegalArgumentException e){ + } + + statAggregator = statAggregator.withSampleRate(1); + statAggregator.add(10); + statAggregator.add(10); + assertThat(statAggregator.valueList.size()).isEqualTo(2); + + statAggregator.flush(0); //Clear value list + statAggregator = statAggregator.withSampleRate(2); + + statAggregator.add(10); + assertThat(statAggregator.valueList.size()).isEqualTo(0); + assertThat(statAggregator.valueList.size()).isEqualTo(0); + + statAggregator.add(10); + assertThat(statAggregator.valueList.size()).isEqualTo(1); + + statAggregator.add(10); + assertThat(statAggregator.valueList.size()).isEqualTo(1); + + statAggregator.add(10); + assertThat(statAggregator.valueList.size()).isEqualTo(2); + + // Test upper bound + statAggregator = statAggregator.withSampleRate(999).withSampleRate(1000); + assertThat(statAggregator.getSampleRate()).isEqualTo(1000); + + try { + statAggregator = statAggregator.withSampleRate(1001); + fail("expected IllegalArgumentException"); + } catch (IllegalArgumentException e){ + } + } + + @Test + public void testWithUnit() { + FakeLoggerBackend backend = new FakeLoggerBackend(); + StatAggregator statAggregator = create("test", 3, backend); + + assertThat(statAggregator.getUnit()).isNull(); + + String unit = "ms"; + statAggregator = statAggregator.withUnit(unit); + assertThat(statAggregator.getUnit()).isEqualTo(unit); + + statAggregator.add(10); + statAggregator.flush(0); + + backend.assertLogged(0).metadata().containsUniqueEntry(StatAggregator.Key.UNIT_STRING, unit); + } + + @Test + public void testSample() { + FakeLoggerBackend backend = new FakeLoggerBackend(); + StatAggregator statAggregator = create("test", 3, backend); + + assertThat(statAggregator.sample()).isTrue(); + assertThat(statAggregator.sample()).isTrue(); + assertThat(statAggregator.sample()).isTrue(); + + statAggregator = statAggregator.withSampleRate(2); + assertThat(statAggregator.sample()).isFalse(); + assertThat(statAggregator.sample()).isTrue(); + assertThat(statAggregator.sample()).isFalse(); + assertThat(statAggregator.sample()).isTrue(); + assertThat(statAggregator.sample()).isFalse(); + } + + @Test + public void testHaveData() { + FakeLoggerBackend backend = new FakeLoggerBackend(); + StatAggregator statAggregator = create("test", 3, backend); + + assertThat(statAggregator.haveData()).isEqualTo(0); + statAggregator.add(10); + assertThat(statAggregator.haveData()).isEqualTo(1); + + statAggregator.flush(0); + assertThat(statAggregator.haveData()).isEqualTo(0); + } + + @Test + public void testAdd() throws InterruptedException { + String name = "test"; + FakeLoggerBackend backend = new FakeLoggerBackend(); + StatAggregator statAggregator = create(name, 4, backend); + + assertThat(statAggregator.getName()).isEqualTo(name); + + // Test value list is full + statAggregator = statAggregator.withNumberWindow(4); + for(int i = 0; i < 5; i++) { + statAggregator.add(10); + } + Thread.sleep(10); //wait for async flush finished. + String message = "test\n" + + "min:10, max:10, total:40, count:4, avg:10.0."; + backend.assertLogged(0).hasMessage(message); + + // Test flush based on number window + statAggregator = statAggregator.withNumberWindow(3); + statAggregator.add(10); + statAggregator.add(10); + Thread.sleep(10); // Wait for async flush finished. + + message = "test\n" + + "min:10, max:10, total:30, count:3, avg:10.0."; + backend.assertLogged(1).hasMessage(message); + } +} From f995fd98969d4b8385e281fa143d3163d7e5d8f4 Mon Sep 17 00:00:00 2001 From: wallace Date: Thu, 7 Jan 2021 17:13:34 +0800 Subject: [PATCH 6/8] refactoring interface --- .../common/flogger/AggregatedLogContext.java | 28 ------------------- .../common/flogger/AggregatedLoggingApi.java | 24 ++++++++++++++++ .../common/flogger/EventAggregator.java | 6 ++-- .../google/common/flogger/StatAggregator.java | 6 ++-- 4 files changed, 30 insertions(+), 34 deletions(-) diff --git a/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java b/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java index 1205fe29..28ab3f82 100644 --- a/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java +++ b/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java @@ -87,34 +87,6 @@ public void run() { protected abstract API self(); - /** - * Check if there are enough data to log based on number window configuration. - * - * @return true: flush now; false: not flush. - */ - protected abstract boolean shouldFlushByNumber(); - - /** - * Check if there are some data to log. - * - * @return the amount of data - */ - protected abstract int haveData(); - - /** - * - * - * @return the string - */ - /** - * Format aggregated data to string for logging. - * - * @param count the amount of data, 0: all, >0: specified amount - * - * @return formatted string content for LogData - */ - protected abstract String message(int count); - /** * Instantiates a new AggregatedLogContext. * diff --git a/api/src/main/java/com/google/common/flogger/AggregatedLoggingApi.java b/api/src/main/java/com/google/common/flogger/AggregatedLoggingApi.java index f029bc66..0a8a44d6 100644 --- a/api/src/main/java/com/google/common/flogger/AggregatedLoggingApi.java +++ b/api/src/main/java/com/google/common/flogger/AggregatedLoggingApi.java @@ -47,4 +47,28 @@ public interface AggregatedLoggingApi { API withNumberWindow(int number); int getNumberWindow(); + + /** + * Check if there are enough data to log based on number window configuration. + * + * @return true: flush now; false: not flush. + */ + boolean shouldFlushByNumber(); + + /** + * Check if there are some data to log. + * + * @return the amount of data to be logged + */ + int haveData(); + + /** + * Format aggregated data to string for logging. + * + * @param count the amount of data, 0: all, >0: specified amount + * + * @return formatted string content for LogData + */ + String message(int count); + } diff --git a/api/src/main/java/com/google/common/flogger/EventAggregator.java b/api/src/main/java/com/google/common/flogger/EventAggregator.java index f955459a..efc5ffd0 100644 --- a/api/src/main/java/com/google/common/flogger/EventAggregator.java +++ b/api/src/main/java/com/google/common/flogger/EventAggregator.java @@ -140,17 +140,17 @@ public void add(String event, String content) { } @Override - protected boolean shouldFlushByNumber() { + public boolean shouldFlushByNumber() { return eventList.size() >= getNumberWindow(); } @Override - protected int haveData() { + public int haveData() { return eventList.size(); } @Override - protected String message(int count){ + public String message(int count){ Checks.checkArgument(count >= 0, "count should be >=0"); List eventBuffer = new ArrayList(); diff --git a/api/src/main/java/com/google/common/flogger/StatAggregator.java b/api/src/main/java/com/google/common/flogger/StatAggregator.java index 1507daf4..7d07eaeb 100644 --- a/api/src/main/java/com/google/common/flogger/StatAggregator.java +++ b/api/src/main/java/com/google/common/flogger/StatAggregator.java @@ -151,17 +151,17 @@ public void add(long value){ } @Override - protected boolean shouldFlushByNumber() { + public boolean shouldFlushByNumber() { return valueList.size() >= getNumberWindow(); } @Override - protected int haveData() { + public int haveData() { return valueList.size(); } @Override - protected String message(int count) { + public String message(int count) { Checks.checkArgument(count >= 0, "count should be larger than 0"); List valueBuffer = new ArrayList(); From 884f81e0f8c88b9b1acdeeed65944d1d90684385 Mon Sep 17 00:00:00 2001 From: wallace Date: Thu, 7 Jan 2021 18:22:13 +0800 Subject: [PATCH 7/8] reformat code --- .../common/flogger/AggregatedLogContext.java | 392 +++++++++--------- .../common/flogger/AggregatedLoggingApi.java | 79 ++-- .../common/flogger/EventAggregator.java | 286 ++++++------- .../flogger/FluentAggregatedLogger.java | 146 +++---- .../google/common/flogger/StatAggregator.java | 365 ++++++++-------- .../flogger/AggregatedLogContextTest.java | 32 +- .../common/flogger/EventAggregatorTest.java | 28 +- .../flogger/FluentAggregatedLoggerTest.java | 6 +- .../common/flogger/StatAggregatorTest.java | 16 +- 9 files changed, 671 insertions(+), 679 deletions(-) diff --git a/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java b/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java index 28ab3f82..4a21b381 100644 --- a/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java +++ b/api/src/main/java/com/google/common/flogger/AggregatedLogContext.java @@ -38,204 +38,196 @@ */ @CheckReturnValue public abstract class AggregatedLogContext implements AggregatedLoggingApi { - - /** - * Available configuration Key - */ - public static final class Key { - private Key() {} - - /** - * Time window for aggregating log. - * {@link AbstractLogger} will log all data at the end of the time window. - */ - public static final MetadataKey TIME_WINDOW = - MetadataKey.single("time_window", Integer.class); - - /** - * Number window for aggregating log. - * {@link AbstractLogger} will log data when the number of data is up to number window. - */ - public static final MetadataKey NUMBER_WINDOW = - MetadataKey.single("number_window", Integer.class); - } - - private final class LogFlusher implements Runnable { - @Override - public void run() { - flush(0); // Always flush all data - - // Write one more log to show timer is running when there is no any data. - LogData logData = getLogData(getName() + " periodically flush log finished"); - getLogger().write(logData); - } - } - - //Runnable for time window - private volatile LogFlusher flusher; - - //Only one thread will flush log - private AtomicBoolean flushLock = new AtomicBoolean(false); - - protected final MutableMetadata metadata = new MutableMetadata(); - - protected final String name; - protected final FluentAggregatedLogger logger; - protected final LogSite logSite; - protected final ScheduledExecutorService pool; - - protected abstract API self(); - - /** - * Instantiates a new AggregatedLogContext. - * - * @param name the name - * @param logger the logger (see {@link FluentAggregatedLogger}). - * @param logSite the log site (see {@link LogSite}). - * @param pool the executor service pool used to periodically log data. - */ - protected AggregatedLogContext(String name, FluentAggregatedLogger logger, LogSite logSite, ScheduledExecutorService pool){ - this.name = checkNotNull(name, "name"); - this.logger = checkNotNull(logger, "logger"); - this.logSite = checkNotNull(logSite, "logSite"); - this.pool = checkNotNull(pool, "pool"); - } - - public String getName() { - return name; - } - - /** - * Schedule log flusher at fixed rate of time window. - * Please call withTimeWindow() before start() to set time window. - * - */ - public synchronized API start(){ - if(flusher != null){ - return self(); - } - - int period = getTimeWindow(); - flusher = new LogFlusher(); - pool.scheduleAtFixedRate(flusher, 2, period, TimeUnit.SECONDS); - - return self(); - } - - @Override - public API withTimeWindow(int seconds) { - if(flusher != null){ - throw new RuntimeException("Please do not change time window after logger start."); - } - - Checks.checkArgument(seconds > 0 && seconds <= 3600, - "Time window range should be (0,3600]"); - - metadata.addValue(Key.TIME_WINDOW, seconds); - - return self(); - } - - @Override - public API withNumberWindow(int number) { - Checks.checkArgument(number > 0 && number <= 1000 * 1000, - "Number window range should be (0, 1000000])"); - - metadata.addValue(Key.NUMBER_WINDOW, number); - return self(); - } - - /** - * Get time window configuration. Default value is 60(seconds). - * - */ - @Override - public int getTimeWindow() { - Integer timeWindow = metadata.findValue(Key.TIME_WINDOW); - return timeWindow == null ? 60 : timeWindow; // Default 60 seconds - } - - /** - * Get number window configuration. Default value is 100. - * - */ - @Override - public int getNumberWindow() { - Integer numberWindow = metadata.findValue(Key.NUMBER_WINDOW); - return numberWindow == null ? 100 : numberWindow; // Default 100 - } - - /** - * Gets metadata. - * - * @return the metadata - */ - protected Metadata getMetadata() { - return metadata != null ? metadata : Metadata.empty(); - - } - - /** - * Gets logger. - * - * @return the logger - */ - protected FluentAggregatedLogger getLogger() { - return logger; - } - - /** - * Generate {@link LogData} for logger backend. - * - * @return the log data - */ - protected LogData getLogData(String message) { - long timestampNanos = Platform.getCurrentTimeNanos(); - String loggerName = getLogger().getBackend().getLoggerName(); - - DefaultLogData logData = new DefaultLogData(timestampNanos, loggerName); - logData.setMetadata(getMetadata()); - - logData.setLogSite(logSite); - logData.setTemplateContext(new TemplateContext(DefaultPrintfMessageParser.getInstance(), message)); - - //use empty array for avoiding null exception - logData.setArgs(new Object[]{}); - - return logData; - } - - /** - * Flush aggregated data in new Thread. - */ - protected void asyncFlush(final int count){ - new Thread(new Runnable() { - @Override - public void run() { - flush(count); - } - }).start(); - } - - /** - * Visible for test.Because real logging action is done in different thread, - * junit will not get the async logging data when running unit test. - * So call log method directly in junit testcase. - */ - protected void flush(int count){ - if(flushLock.compareAndSet(false,true)) { - try { - if(count > 0 && haveData() < count){ - return; - } - - LogData logData = getLogData(message(count)); - getLogger().write(logData); - } - finally { - flushLock.compareAndSet(true, false); - } - } - } + API extends AggregatedLoggingApi> implements AggregatedLoggingApi { + + protected final MutableMetadata metadata = new MutableMetadata(); + protected final String name; + protected final FluentAggregatedLogger logger; + protected final LogSite logSite; + protected final ScheduledExecutorService pool; + //Runnable for time window + private volatile LogFlusher flusher; + //Only one thread will flush log + private AtomicBoolean flushLock = new AtomicBoolean(false); + /** + * Instantiates a new AggregatedLogContext. + * + * @param name the name + * @param logger the logger (see {@link FluentAggregatedLogger}). + * @param logSite the log site (see {@link LogSite}). + * @param pool the executor service pool used to periodically log data. + */ + protected AggregatedLogContext(String name, FluentAggregatedLogger logger, LogSite logSite, ScheduledExecutorService pool) { + this.name = checkNotNull(name, "name"); + this.logger = checkNotNull(logger, "logger"); + this.logSite = checkNotNull(logSite, "logSite"); + this.pool = checkNotNull(pool, "pool"); + } + + protected abstract API self(); + + public String getName() { + return name; + } + + /** + * Schedule log flusher at fixed rate of time window. + * Please call withTimeWindow() before start() to set time window. + */ + public synchronized API start() { + if (flusher != null) { + return self(); + } + + int period = getTimeWindow(); + flusher = new LogFlusher(); + pool.scheduleAtFixedRate(flusher, 2, period, TimeUnit.SECONDS); + + return self(); + } + + @Override + public API withTimeWindow(int seconds) { + if (flusher != null) { + throw new RuntimeException("Please do not change time window after logger start."); + } + + Checks.checkArgument(seconds > 0 && seconds <= 3600, + "Time window range should be (0,3600]"); + + metadata.addValue(Key.TIME_WINDOW, seconds); + + return self(); + } + + @Override + public API withNumberWindow(int number) { + Checks.checkArgument(number > 0 && number <= 1000 * 1000, + "Number window range should be (0, 1000000])"); + + metadata.addValue(Key.NUMBER_WINDOW, number); + return self(); + } + + /** + * Get time window configuration. Default value is 60(seconds). + */ + @Override + public int getTimeWindow() { + Integer timeWindow = metadata.findValue(Key.TIME_WINDOW); + return timeWindow == null ? 60 : timeWindow; // Default 60 seconds + } + + /** + * Get number window configuration. Default value is 100. + */ + @Override + public int getNumberWindow() { + Integer numberWindow = metadata.findValue(Key.NUMBER_WINDOW); + return numberWindow == null ? 100 : numberWindow; // Default 100 + } + + /** + * Gets metadata. + * + * @return the metadata + */ + protected Metadata getMetadata() { + return metadata != null ? metadata : Metadata.empty(); + + } + + /** + * Gets logger. + * + * @return the logger + */ + protected FluentAggregatedLogger getLogger() { + return logger; + } + + /** + * Generate {@link LogData} for logger backend. + * + * @return the log data + */ + protected LogData getLogData(String message) { + long timestampNanos = Platform.getCurrentTimeNanos(); + String loggerName = getLogger().getBackend().getLoggerName(); + + DefaultLogData logData = new DefaultLogData(timestampNanos, loggerName); + logData.setMetadata(getMetadata()); + + logData.setLogSite(logSite); + logData.setTemplateContext(new TemplateContext(DefaultPrintfMessageParser.getInstance(), message)); + + //use empty array for avoiding null exception + logData.setArgs(new Object[]{}); + + return logData; + } + + /** + * Flush aggregated data in new Thread. + */ + protected void asyncFlush(final int count) { + new Thread(new Runnable() { + @Override + public void run() { + flush(count); + } + }).start(); + } + + /** + * Visible for test.Because real logging action is done in different thread, + * junit will not get the async logging data when running unit test. + * So call log method directly in junit testcase. + */ + protected void flush(int count) { + if (flushLock.compareAndSet(false, true)) { + try { + if (count > 0 && haveData() < count) { + return; + } + + LogData logData = getLogData(message(count)); + getLogger().write(logData); + } finally { + flushLock.compareAndSet(true, false); + } + } + } + + /** + * Available configuration Key + */ + public static final class Key { + /** + * Time window for aggregating log. + * {@link AbstractLogger} will log all data at the end of the time window. + */ + public static final MetadataKey TIME_WINDOW = + MetadataKey.single("time_window", Integer.class); + /** + * Number window for aggregating log. + * {@link AbstractLogger} will log data when the number of data is up to number window. + */ + public static final MetadataKey NUMBER_WINDOW = + MetadataKey.single("number_window", Integer.class); + + private Key() { + } + } + + private final class LogFlusher implements Runnable { + @Override + public void run() { + flush(0); // Always flush all data + + // Write one more log to show timer is running when there is no any data. + LogData logData = getLogData(getName() + " periodically flush log finished"); + getLogger().write(logData); + } + } } diff --git a/api/src/main/java/com/google/common/flogger/AggregatedLoggingApi.java b/api/src/main/java/com/google/common/flogger/AggregatedLoggingApi.java index 0a8a44d6..868ff684 100644 --- a/api/src/main/java/com/google/common/flogger/AggregatedLoggingApi.java +++ b/api/src/main/java/com/google/common/flogger/AggregatedLoggingApi.java @@ -24,51 +24,50 @@ */ @CheckReturnValue public interface AggregatedLoggingApi { - /** - * Set aggregated logger time window. - * If time window is set, aggregated logger will periodically flush log. - * - * @param seconds - * @return - */ - API withTimeWindow(int seconds); + /** + * Set aggregated logger time window. + * If time window is set, aggregated logger will periodically flush log. + * + * @param seconds + * @return + */ + API withTimeWindow(int seconds); - int getTimeWindow(); + int getTimeWindow(); - /** - * Set aggregated logger number window. - * - * If number window is set, aggregated logger will flush log when log number - * is equal or more than number window. - * - * @param number - * @return - */ - API withNumberWindow(int number); + /** + * Set aggregated logger number window. + *

+ * If number window is set, aggregated logger will flush log when log number + * is equal or more than number window. + * + * @param number + * @return + */ + API withNumberWindow(int number); - int getNumberWindow(); + int getNumberWindow(); - /** - * Check if there are enough data to log based on number window configuration. - * - * @return true: flush now; false: not flush. - */ - boolean shouldFlushByNumber(); + /** + * Check if there are enough data to log based on number window configuration. + * + * @return true: flush now; false: not flush. + */ + boolean shouldFlushByNumber(); - /** - * Check if there are some data to log. - * - * @return the amount of data to be logged - */ - int haveData(); + /** + * Check if there are some data to log. + * + * @return the amount of data to be logged + */ + int haveData(); - /** - * Format aggregated data to string for logging. - * - * @param count the amount of data, 0: all, >0: specified amount - * - * @return formatted string content for LogData - */ - String message(int count); + /** + * Format aggregated data to string for logging. + * + * @param count the amount of data, 0: all, >0: specified amount + * @return formatted string content for LogData + */ + String message(int count); } diff --git a/api/src/main/java/com/google/common/flogger/EventAggregator.java b/api/src/main/java/com/google/common/flogger/EventAggregator.java index efc5ffd0..403d3cb1 100644 --- a/api/src/main/java/com/google/common/flogger/EventAggregator.java +++ b/api/src/main/java/com/google/common/flogger/EventAggregator.java @@ -34,153 +34,155 @@ *

* EventAggregator can aggregate many same type events and log the key information within one log. * For example: - * requestId=1053:200 | requestId=1054:200 | requestId=1055:500 | requestId=1056:200 | requestId=1057:200 | - * requestId=1063:200 | requestId=1064:200 | requestId=1065:200 | requestId=1066:404 | requestId=1067:200 | + * requestId=1053:200 | requestId=1054:200 | requestId=1055:500 | requestId=1056:200 | requestId=1057:200 | + * requestId=1063:200 | requestId=1064:200 | requestId=1065:200 | requestId=1066:404 | requestId=1067:200 | *

* The best practice is to use {@link EventAggregator} to log brief information for all api request and response, * and use {@link FluentLogger} to log detailed information for error requests and responses. - * */ @CheckReturnValue public class EventAggregator extends AggregatedLogContext { - /** - * Simple Event pair. - */ - static final class EventPair { - private final String key; - private final String value; - - /** - * Instantiates a new Event pair. - * - * @param key the key - * @param value the value - */ - public EventPair(String key, String value){ - this.key = key; - this.value = value; - } - - /** - * Gets key. - * - * @return the key - */ - public String getKey() { - return key; - } - - /** - * Gets value. - * - * @return the value - */ - public String getValue() { - return value; - } - } - - /** - * Use LinkedBlockingQueue to store events. - *

- * Two reasons for using LinkedBlockingQueue: - * 1. Thread-safe: many threads will use the same {@link EventAggregator} to log same type events. - * 2. Async log: logging aggregated events is a time-consuming action. It's better to use separate thread to do it. - */ - protected final BlockingQueue eventList; - /** - * Instantiates a new Event aggregator. - * - * @param name the name - * @param logger the logger (see {@link FluentAggregatedLogger}). - * @param logSite the log site (see {@link LogSite}). - * @param pool the executor service pool used to periodically log data. - */ - EventAggregator(String name, FluentAggregatedLogger logger, LogSite logSite, - ScheduledExecutorService pool, int capacity){ - super(name, logger, logSite, pool); - eventList = new LinkedBlockingQueue(capacity); - } - - @Override - protected EventAggregator self() { - return this; - } - - /** - * Add event to {@link EventAggregator}. - * - * @param event the event - * @param content the content - */ - public void add(String event, String content) { - //try 3 times - int i = 0; - while(i++ < 3) { - try { - if(eventList.offer(new EventPair(event, content), 1, TimeUnit.MILLISECONDS)) { - if(shouldFlushByNumber()){ - asyncFlush(getNumberWindow()); - } - - break; - } else { - //If BlockingQueue is full, just immediately flush - asyncFlush(0); - Thread.sleep(1); - } - } catch (InterruptedException e) { - if(i == 2) { - //Do not log anything, just print stacktrace - e.printStackTrace(); - } - }; - }; - } - - @Override - public boolean shouldFlushByNumber() { - return eventList.size() >= getNumberWindow(); - } - - @Override - public int haveData() { - return eventList.size(); - } - - @Override - public String message(int count){ - Checks.checkArgument(count >= 0, "count should be >=0"); - - List eventBuffer = new ArrayList(); - if(count == 0){ - eventList.drainTo(eventBuffer); - } else { - eventList.drainTo(eventBuffer, count); - } - - return formatMessage(eventBuffer); - } - - private String formatMessage(List eventBuffer){ - int bufferSize = eventBuffer.size(); - StringBuilder builder = new StringBuilder(); - - builder.append(name).append("\n"); - for( int i = 0; i < bufferSize; i++){ - builder.append(eventBuffer.get(i).getKey()).append(":") - .append(eventBuffer.get(i).getValue()).append(" | "); - if((i+1) % 10 == 0){ - builder.append("\n"); - } - } - if(bufferSize % 10 != 0){ - builder.append("\n"); - } - - builder.append("\ntotal: ").append(bufferSize); - - return builder.toString(); - } + /** + * Use LinkedBlockingQueue to store events. + *

+ * Two reasons for using LinkedBlockingQueue: + * 1. Thread-safe: many threads will use the same {@link EventAggregator} to log same type events. + * 2. Async log: logging aggregated events is a time-consuming action. It's better to use separate thread to do it. + */ + protected final BlockingQueue eventList; + + /** + * Instantiates a new Event aggregator. + * + * @param name the name + * @param logger the logger (see {@link FluentAggregatedLogger}). + * @param logSite the log site (see {@link LogSite}). + * @param pool the executor service pool used to periodically log data. + */ + EventAggregator(String name, FluentAggregatedLogger logger, LogSite logSite, + ScheduledExecutorService pool, int capacity) { + super(name, logger, logSite, pool); + eventList = new LinkedBlockingQueue(capacity); + } + + @Override + protected EventAggregator self() { + return this; + } + + /** + * Add event to {@link EventAggregator}. + * + * @param event the event + * @param content the content + */ + public void add(String event, String content) { + //try 3 times + int i = 0; + while (i++ < 3) { + try { + if (eventList.offer(new EventPair(event, content), 1, TimeUnit.MILLISECONDS)) { + if (shouldFlushByNumber()) { + asyncFlush(getNumberWindow()); + } + + break; + } else { + //If BlockingQueue is full, just immediately flush + asyncFlush(0); + Thread.sleep(1); + } + } catch (InterruptedException e) { + if (i == 2) { + //Do not log anything, just print stacktrace + e.printStackTrace(); + } + } + ; + } + ; + } + + @Override + public boolean shouldFlushByNumber() { + return eventList.size() >= getNumberWindow(); + } + + @Override + public int haveData() { + return eventList.size(); + } + + @Override + public String message(int count) { + Checks.checkArgument(count >= 0, "count should be >=0"); + + List eventBuffer = new ArrayList(); + if (count == 0) { + eventList.drainTo(eventBuffer); + } else { + eventList.drainTo(eventBuffer, count); + } + + return formatMessage(eventBuffer); + } + + private String formatMessage(List eventBuffer) { + int bufferSize = eventBuffer.size(); + StringBuilder builder = new StringBuilder(); + + builder.append(name).append("\n"); + for (int i = 0; i < bufferSize; i++) { + builder.append(eventBuffer.get(i).getKey()).append(":") + .append(eventBuffer.get(i).getValue()).append(" | "); + if ((i + 1) % 10 == 0) { + builder.append("\n"); + } + } + if (bufferSize % 10 != 0) { + builder.append("\n"); + } + + builder.append("\ntotal: ").append(bufferSize); + + return builder.toString(); + } + + /** + * Simple Event pair. + */ + static final class EventPair { + private final String key; + private final String value; + + /** + * Instantiates a new Event pair. + * + * @param key the key + * @param value the value + */ + public EventPair(String key, String value) { + this.key = key; + this.value = value; + } + + /** + * Gets key. + * + * @return the key + */ + public String getKey() { + return key; + } + + /** + * Gets value. + * + * @return the value + */ + public String getValue() { + return value; + } + } } diff --git a/api/src/main/java/com/google/common/flogger/FluentAggregatedLogger.java b/api/src/main/java/com/google/common/flogger/FluentAggregatedLogger.java index ea5e8e12..48992867 100644 --- a/api/src/main/java/com/google/common/flogger/FluentAggregatedLogger.java +++ b/api/src/main/java/com/google/common/flogger/FluentAggregatedLogger.java @@ -34,77 +34,77 @@ */ @CheckReturnValue public final class FluentAggregatedLogger extends AbstractLogger { - //Executor service pool for all aggregated loggers to periodically flush log. - private static final ScheduledExecutorService pool = Executors.newScheduledThreadPool(8); - - //Cache the AggregatedLogContext instance for multi threads - private final Map aggregatorMap = new ConcurrentHashMap(); - - //Visible for unit testing - protected FluentAggregatedLogger(LoggerBackend backend) { - super(backend); - } - - public static FluentAggregatedLogger forEnclosingClass() { - // NOTE: It is _vital_ that the call to "caller finder" is made directly inside the static - // factory method. See getCallerFinder() for more information. - String loggingClass = Platform.getCallerFinder().findLoggingClass(FluentAggregatedLogger.class); - return new FluentAggregatedLogger(Platform.getBackend(loggingClass)); - } - - /** - * Get EventAggregator - * - * @param name aggregator logger name, should be unique in the same FluentAggregatedLogger scope. - * @return EventAggregator - */ - public EventAggregator getEvent(String name ){ - AggregatedLogContext aggregator = aggregatorMap.get(name); - if(aggregator == null){ - //Get logsite here for async write data in new Thread - LogSite logSite = checkNotNull(Platform.getCallerFinder().findLogSite(FluentAggregatedLogger.class, 0), - "logger backend must not return a null LogSite"); - aggregator = new EventAggregator(name, this, logSite, pool, 1024 * 1024); - aggregatorMap.putIfAbsent(name, aggregator); - - AggregatedLogContext old = aggregatorMap.putIfAbsent(name, aggregator); - - if( old != null ) { - aggregator = old; - } - } - - if( !(aggregator instanceof EventAggregator)){ - throw new RuntimeException("There is another kind of logger with the same name"); - } - - return (EventAggregator)aggregator; - } - - /** - * Get StatAggregator - * - * @param name aggregator logger name, should be unique in the same FluentAggregatedLogger scope. - * @return StatAggregator - */ - public StatAggregator getStat(String name ){ - AggregatedLogContext aggregator = aggregatorMap.get(name); - if(aggregator == null){ - //Get logsite here for async write data in new Thread - LogSite logSite = checkNotNull(Platform.getCallerFinder().findLogSite(FluentAggregatedLogger.class, 0), - "logger backend must not return a null LogSite"); - aggregator = new StatAggregator(name, this, logSite, pool, 1024 * 1024); - AggregatedLogContext old = aggregatorMap.putIfAbsent(name, aggregator); - - if( old != null ) { - aggregator = old; - } - } - - if( !(aggregator instanceof StatAggregator)){ - throw new RuntimeException("There is another kind of logger with the same name: " + name); - } - - return (StatAggregator)aggregator; - } + //Executor service pool for all aggregated loggers to periodically flush log. + private static final ScheduledExecutorService pool = Executors.newScheduledThreadPool(8); + + //Cache the AggregatedLogContext instance for multi threads + private final Map aggregatorMap = new ConcurrentHashMap(); + + //Visible for unit testing + protected FluentAggregatedLogger(LoggerBackend backend) { + super(backend); + } + + public static FluentAggregatedLogger forEnclosingClass() { + // NOTE: It is _vital_ that the call to "caller finder" is made directly inside the static + // factory method. See getCallerFinder() for more information. + String loggingClass = Platform.getCallerFinder().findLoggingClass(FluentAggregatedLogger.class); + return new FluentAggregatedLogger(Platform.getBackend(loggingClass)); + } + + /** + * Get EventAggregator + * + * @param name aggregator logger name, should be unique in the same FluentAggregatedLogger scope. + * @return EventAggregator + */ + public EventAggregator getEvent(String name) { + AggregatedLogContext aggregator = aggregatorMap.get(name); + if (aggregator == null) { + //Get logsite here for async write data in new Thread + LogSite logSite = checkNotNull(Platform.getCallerFinder().findLogSite(FluentAggregatedLogger.class, 0), + "logger backend must not return a null LogSite"); + aggregator = new EventAggregator(name, this, logSite, pool, 1024 * 1024); + aggregatorMap.putIfAbsent(name, aggregator); + + AggregatedLogContext old = aggregatorMap.putIfAbsent(name, aggregator); + + if (old != null) { + aggregator = old; + } + } + + if (!(aggregator instanceof EventAggregator)) { + throw new RuntimeException("There is another kind of logger with the same name"); + } + + return (EventAggregator) aggregator; + } + + /** + * Get StatAggregator + * + * @param name aggregator logger name, should be unique in the same FluentAggregatedLogger scope. + * @return StatAggregator + */ + public StatAggregator getStat(String name) { + AggregatedLogContext aggregator = aggregatorMap.get(name); + if (aggregator == null) { + //Get logsite here for async write data in new Thread + LogSite logSite = checkNotNull(Platform.getCallerFinder().findLogSite(FluentAggregatedLogger.class, 0), + "logger backend must not return a null LogSite"); + aggregator = new StatAggregator(name, this, logSite, pool, 1024 * 1024); + AggregatedLogContext old = aggregatorMap.putIfAbsent(name, aggregator); + + if (old != null) { + aggregator = old; + } + } + + if (!(aggregator instanceof StatAggregator)) { + throw new RuntimeException("There is another kind of logger with the same name: " + name); + } + + return (StatAggregator) aggregator; + } } diff --git a/api/src/main/java/com/google/common/flogger/StatAggregator.java b/api/src/main/java/com/google/common/flogger/StatAggregator.java index 7d07eaeb..9061578a 100644 --- a/api/src/main/java/com/google/common/flogger/StatAggregator.java +++ b/api/src/main/java/com/google/common/flogger/StatAggregator.java @@ -31,193 +31,192 @@ *

* StatAggregator can aggregate many same type performance data and simply calc the min/max/total/avg value. * For example: - * API: get-user-api - * min:60, max:87, total:735, count:10, avg:73.5. - * [CONTEXT number_window=10 sample_rate=3 unit="ms" ] + * API: get-user-api + * min:60, max:87, total:735, count:10, avg:73.5. + * [CONTEXT number_window=10 sample_rate=3 unit="ms" ] *

* You can combine {@link EventAggregator} and {@link StatAggregator} and {@link FluentLogger} * to log full information for API or other kind of event like this: - * - use {@link EventAggregator} to log key information like request id and response code. - * - use {@link StatAggregator} to log performance information - * - use {@link FluentLogger} to log detailed information for error requests or responses. - * + * - use {@link EventAggregator} to log key information like request id and response code. + * - use {@link StatAggregator} to log performance information + * - use {@link FluentLogger} to log detailed information for error requests or responses. */ public class StatAggregator extends AggregatedLogContext { - public static final class Key { - private Key() { - } - - public static final MetadataKey SAMPLE_RATE = - MetadataKey.single("sample_rate", Integer.class); - - public static final MetadataKey UNIT_STRING = - MetadataKey.single("unit", String.class); - } - - //Only calc some data - private volatile int sampleRate = 1; //Calc all data by default. - private final AtomicInteger sampleCounter = new AtomicInteger(1); - - /** - * Use LinkedBlockingQueue to store values. - *

- * Two reasons for using LinkedBlockingQueue: - * 1. Thread-safe: many threads will use the same {@link StatAggregator} to log same type values. - * 2. Async log: logging aggregated value is a time-consuming action. It's better to use separate thread to do it. - */ - protected final BlockingQueue valueList; - - protected StatAggregator(String name, FluentAggregatedLogger logger, LogSite logSite, - ScheduledExecutorService pool, int capacity) { - super(name, logger, logSite, pool); - valueList = new LinkedBlockingQueue(capacity); - } - - @Override - protected StatAggregator self() { - return this; - } - - /** - * Set sample rate - * - * @param sampleRate - * @return - */ - public StatAggregator withSampleRate(int sampleRate){ - Checks.checkArgument(sampleRate > 0 && sampleRate <= 1000, - "Sample rate range should be (0,1000]"); - - this.sampleRate = sampleRate; - metadata.addValue(Key.SAMPLE_RATE, sampleRate); //Just for log context - - return self(); - } - - public int getSampleRate(){ - return sampleRate; - } - - /** - * Set unit for log context. - * - * @param unit - * @return - */ - public StatAggregator withUnit(String unit){ - metadata.addValue(Key.UNIT_STRING, unit); - - return self(); - } - - public String getUnit(){ - String unit = metadata.findValue(Key.UNIT_STRING); - return unit; - } - - /** - * Add value - * - * @param value - */ - public void add(long value){ - if(!sample()){ - return; - } - - //try 3 times - int i = 0; - while(i++ < 3) { - try { - if(valueList.offer(value, 1, TimeUnit.MILLISECONDS)) { - if(shouldFlushByNumber()){ - asyncFlush(getNumberWindow()); - } - - break; - } else { - //If BlockingQueue is full, just immediately flush - asyncFlush(0); - Thread.sleep(1); - } - } catch (InterruptedException e) { - if(i == 2) { - //Do not log anything, just print stacktrace - e.printStackTrace(); - } - }; - }; - } - - @Override - public boolean shouldFlushByNumber() { - return valueList.size() >= getNumberWindow(); - } - - @Override - public int haveData() { - return valueList.size(); - } - - @Override - public String message(int count) { - Checks.checkArgument(count >= 0, "count should be larger than 0"); - - List valueBuffer = new ArrayList(); - if(count == 0){ - valueList.drainTo(valueBuffer); - } else { - valueList.drainTo(valueBuffer, count); - } - - return formatMessage(valueBuffer); - } - - /** - * Sample data - * - * @return true: log; false: skip - */ - protected boolean sample(){ - return sampleRate == 1 || (sampleCounter.getAndIncrement() % sampleRate == 0); - } - - private String formatMessage(List valueBuffer){ - - long min = Long.MAX_VALUE; - long max = Long.MIN_VALUE; - long total = 0; - double avg = 0; - - for(Long e : valueBuffer){ - if(e > max){ - max = e; - } - - if(e < min){ - min = e; - } - - total += e; - } - - StringBuilder builder = new StringBuilder(); - builder.append(name).append("\n"); - - if(!valueBuffer.isEmpty()) { - avg = Double.valueOf(total) / valueBuffer.size(); - - String sep = ", "; - builder.append("min:").append(min).append(sep) - .append("max:").append(max).append(sep) - .append("total:").append(total).append(sep) - .append("count:").append(valueBuffer.size()).append(sep) - .append("avg:").append(avg).append("."); - } else { - builder.append(" "); - } - - return builder.toString(); - } + /** + * Use LinkedBlockingQueue to store values. + *

+ * Two reasons for using LinkedBlockingQueue: + * 1. Thread-safe: many threads will use the same {@link StatAggregator} to log same type values. + * 2. Async log: logging aggregated value is a time-consuming action. It's better to use separate thread to do it. + */ + protected final BlockingQueue valueList; + private final AtomicInteger sampleCounter = new AtomicInteger(1); + //Only calc some data + private volatile int sampleRate = 1; //Calc all data by default. + + protected StatAggregator(String name, FluentAggregatedLogger logger, LogSite logSite, + ScheduledExecutorService pool, int capacity) { + super(name, logger, logSite, pool); + valueList = new LinkedBlockingQueue(capacity); + } + + @Override + protected StatAggregator self() { + return this; + } + + /** + * Set sample rate + * + * @param sampleRate + * @return + */ + public StatAggregator withSampleRate(int sampleRate) { + Checks.checkArgument(sampleRate > 0 && sampleRate <= 1000, + "Sample rate range should be (0,1000]"); + + this.sampleRate = sampleRate; + metadata.addValue(Key.SAMPLE_RATE, sampleRate); //Just for log context + + return self(); + } + + public int getSampleRate() { + return sampleRate; + } + + /** + * Set unit for log context. + * + * @param unit + * @return + */ + public StatAggregator withUnit(String unit) { + metadata.addValue(Key.UNIT_STRING, unit); + + return self(); + } + + public String getUnit() { + String unit = metadata.findValue(Key.UNIT_STRING); + return unit; + } + + /** + * Add value + * + * @param value + */ + public void add(long value) { + if (!sample()) { + return; + } + + //try 3 times + int i = 0; + while (i++ < 3) { + try { + if (valueList.offer(value, 1, TimeUnit.MILLISECONDS)) { + if (shouldFlushByNumber()) { + asyncFlush(getNumberWindow()); + } + + break; + } else { + //If BlockingQueue is full, just immediately flush + asyncFlush(0); + Thread.sleep(1); + } + } catch (InterruptedException e) { + if (i == 2) { + //Do not log anything, just print stacktrace + e.printStackTrace(); + } + } + ; + } + ; + } + + @Override + public boolean shouldFlushByNumber() { + return valueList.size() >= getNumberWindow(); + } + + @Override + public int haveData() { + return valueList.size(); + } + + @Override + public String message(int count) { + Checks.checkArgument(count >= 0, "count should be larger than 0"); + + List valueBuffer = new ArrayList(); + if (count == 0) { + valueList.drainTo(valueBuffer); + } else { + valueList.drainTo(valueBuffer, count); + } + + return formatMessage(valueBuffer); + } + + /** + * Sample data + * + * @return true: log; false: skip + */ + protected boolean sample() { + return sampleRate == 1 || (sampleCounter.getAndIncrement() % sampleRate == 0); + } + + private String formatMessage(List valueBuffer) { + + long min = Long.MAX_VALUE; + long max = Long.MIN_VALUE; + long total = 0; + double avg = 0; + + for (Long e : valueBuffer) { + if (e > max) { + max = e; + } + + if (e < min) { + min = e; + } + + total += e; + } + + StringBuilder builder = new StringBuilder(); + builder.append(name).append("\n"); + + if (!valueBuffer.isEmpty()) { + avg = Double.valueOf(total) / valueBuffer.size(); + + String sep = ", "; + builder.append("min:").append(min).append(sep) + .append("max:").append(max).append(sep) + .append("total:").append(total).append(sep) + .append("count:").append(valueBuffer.size()).append(sep) + .append("avg:").append(avg).append("."); + } else { + builder.append(" "); + } + + return builder.toString(); + } + + public static final class Key { + public static final MetadataKey SAMPLE_RATE = + MetadataKey.single("sample_rate", Integer.class); + public static final MetadataKey UNIT_STRING = + MetadataKey.single("unit", String.class); + + private Key() { + } + } } diff --git a/api/src/test/java/com/google/common/flogger/AggregatedLogContextTest.java b/api/src/test/java/com/google/common/flogger/AggregatedLogContextTest.java index 5c059925..845a32a0 100644 --- a/api/src/test/java/com/google/common/flogger/AggregatedLogContextTest.java +++ b/api/src/test/java/com/google/common/flogger/AggregatedLogContextTest.java @@ -29,7 +29,7 @@ public class AggregatedLogContextTest { final static FluentAggregatedLogger logger2 = FluentAggregatedLogger.forEnclosingClass(); @Test - public void testGetName(){ + public void testGetName() { String name = "test"; FakeLoggerBackend backend = new FakeLoggerBackend(); FluentAggregatedLogger logger = new FluentAggregatedLogger(backend); @@ -48,16 +48,16 @@ public void testWithTimeWindow() { try { eventAggregator.withTimeWindow(-1); fail("expected IllegalArgumentException"); - } catch (IllegalArgumentException e){ + } catch (IllegalArgumentException e) { } try { eventAggregator.withTimeWindow(0); fail("expected IllegalArgumentException"); - } catch (IllegalArgumentException e){ + } catch (IllegalArgumentException e) { } - eventAggregator.withTimeWindow(1).add("timewindow","1"); + eventAggregator.withTimeWindow(1).add("timewindow", "1"); eventAggregator.flush(0); backend.assertLogged(0).metadata().containsUniqueEntry(AggregatedLogContext.Key.TIME_WINDOW, 1); @@ -65,29 +65,29 @@ public void testWithTimeWindow() { try { eventAggregator.withTimeWindow(3601); fail("expected IllegalArgumentException"); - } catch (IllegalArgumentException e){ + } catch (IllegalArgumentException e) { } try { eventAggregator.withTimeWindow(10000); fail("expected IllegalArgumentException"); - } catch (IllegalArgumentException e){ + } catch (IllegalArgumentException e) { e.printStackTrace(); } EventAggregator eventAggregator2 = logger.getEvent("test2"); - eventAggregator2.withTimeWindow(3600).add("timewindow","3600"); + eventAggregator2.withTimeWindow(3600).add("timewindow", "3600"); eventAggregator2.flush(0); backend.assertLogged(1).metadata().containsUniqueEntry(AggregatedLogContext.Key.TIME_WINDOW, 3600); // Test repeatedly set - eventAggregator.withTimeWindow(2).add("timewindow","2"); + eventAggregator.withTimeWindow(2).add("timewindow", "2"); eventAggregator.flush(0); backend.assertLogged(2).metadata().containsUniqueEntry(AggregatedLogContext.Key.TIME_WINDOW, 2); try { eventAggregator = eventAggregator.start().withTimeWindow(3); fail("expected RuntimeException"); - } catch (RuntimeException e){ + } catch (RuntimeException e) { } } @@ -101,16 +101,16 @@ public void testWithNumberWindow() { try { eventAggregator.withNumberWindow(-1); fail("expected IllegalArgumentException"); - } catch (IllegalArgumentException e){ + } catch (IllegalArgumentException e) { } try { eventAggregator.withNumberWindow(0); fail("expected IllegalArgumentException"); - } catch (IllegalArgumentException e){ + } catch (IllegalArgumentException e) { } - eventAggregator.withNumberWindow(1).add("numberwindow","1"); + eventAggregator.withNumberWindow(1).add("numberwindow", "1"); eventAggregator.flush(0); backend.assertLogged(0).metadata().containsUniqueEntry(AggregatedLogContext.Key.NUMBER_WINDOW, 1); @@ -118,16 +118,16 @@ public void testWithNumberWindow() { try { eventAggregator.withNumberWindow(1000 * 1000 + 1); fail("expected IllegalArgumentException"); - } catch (IllegalArgumentException e){ + } catch (IllegalArgumentException e) { } try { eventAggregator.withNumberWindow(1024 * 1024); fail("expected IllegalArgumentException"); - } catch (IllegalArgumentException e){ + } catch (IllegalArgumentException e) { e.printStackTrace(); } - eventAggregator.withNumberWindow(1000 * 1000).add("timewindow","1000 * 1000"); + eventAggregator.withNumberWindow(1000 * 1000).add("timewindow", "1000 * 1000"); eventAggregator.flush(0); backend.assertLogged(1).metadata().containsUniqueEntry(AggregatedLogContext.Key.NUMBER_WINDOW, 1000 * 1000); } @@ -141,7 +141,7 @@ public void testShouldFlush() throws InterruptedException { assertThat(eventAggregator.shouldFlushByNumber()).isFalse(); eventAggregator = eventAggregator.withNumberWindow(10); - for(int i = 0; i < 9; i++){ + for (int i = 0; i < 9; i++) { eventAggregator.add("hello", "world"); } assertThat(eventAggregator.shouldFlushByNumber()).isFalse(); diff --git a/api/src/test/java/com/google/common/flogger/EventAggregatorTest.java b/api/src/test/java/com/google/common/flogger/EventAggregatorTest.java index 84afeaa2..313e785b 100644 --- a/api/src/test/java/com/google/common/flogger/EventAggregatorTest.java +++ b/api/src/test/java/com/google/common/flogger/EventAggregatorTest.java @@ -31,10 +31,10 @@ @RunWith(JUnit4.class) public class EventAggregatorTest { - private EventAggregator create(String name, int capacity, FakeLoggerBackend backend){ + private EventAggregator create(String name, int capacity, FakeLoggerBackend backend) { FluentAggregatedLogger logger = new FluentAggregatedLogger(backend); LogSite logSite = checkNotNull(Platform.getCallerFinder().findLogSite(FluentAggregatedLogger.class, 0), - "logger backend must not return a null LogSite"); + "logger backend must not return a null LogSite"); ScheduledExecutorService pool = Executors.newScheduledThreadPool(1); return new EventAggregator(name, logger, logSite, pool, capacity); @@ -48,23 +48,23 @@ public void testAdd() throws InterruptedException { assertThat(eventAggregator.getName()).isEqualTo(name); - eventAggregator.add("event1","hello"); - eventAggregator.add("event2","world"); - eventAggregator.add("event3","flogger"); + eventAggregator.add("event1", "hello"); + eventAggregator.add("event2", "world"); + eventAggregator.add("event3", "flogger"); Thread.sleep(3); //wait for async flush thread to finish eventAggregator.flush(0); String message1 = "test\n" + - "event1:hello | event2:world | \n" + - "\n" + - "total: 2"; + "event1:hello | event2:world | \n" + + "\n" + + "total: 2"; backend.assertLogged(0).hasMessage(message1); String message2 = "test\n" + - "event3:flogger | \n" + - "\n" + - "total: 1"; + "event3:flogger | \n" + + "\n" + + "total: 1"; backend.assertLogged(1).hasMessage(message2); } @@ -74,13 +74,13 @@ public void testHaveData() { FakeLoggerBackend backend = new FakeLoggerBackend(); EventAggregator eventAggregator = create(name, 2, backend); - eventAggregator.add("event1","hello"); + eventAggregator.add("event1", "hello"); assertThat(eventAggregator.haveData()).isEqualTo(1); - eventAggregator.add("event2","world"); + eventAggregator.add("event2", "world"); assertThat(eventAggregator.haveData()).isEqualTo(2); - eventAggregator.add("event3","flogger"); + eventAggregator.add("event3", "flogger"); assertThat(eventAggregator.haveData()).isEqualTo(1); //Assert.assertEquals(eventAggregator.haveData(),3); diff --git a/api/src/test/java/com/google/common/flogger/FluentAggregatedLoggerTest.java b/api/src/test/java/com/google/common/flogger/FluentAggregatedLoggerTest.java index e3c94084..7972d670 100644 --- a/api/src/test/java/com/google/common/flogger/FluentAggregatedLoggerTest.java +++ b/api/src/test/java/com/google/common/flogger/FluentAggregatedLoggerTest.java @@ -29,7 +29,7 @@ /** * FluentAggregatedLogger is typically very simple classes whose only real responsibility is as a factory * for a specific API implementation. As such it needs very few tests itself. - * + *

* See AggregatedLogContextTest.java for the vast majority of tests related to base logging behaviour. */ @RunWith(JUnit4.class) @@ -65,7 +65,7 @@ public void testGetEvent() { try { StatAggregator statAggregator = logger.getStat(name1); fail("expected RuntimeException"); - } catch (RuntimeException e){ + } catch (RuntimeException e) { } } @@ -95,7 +95,7 @@ public void testGetStat() { try { EventAggregator eventAggregator = logger.getEvent(name1); fail("expected RuntimeException"); - } catch (RuntimeException e){ + } catch (RuntimeException e) { } } diff --git a/api/src/test/java/com/google/common/flogger/StatAggregatorTest.java b/api/src/test/java/com/google/common/flogger/StatAggregatorTest.java index a25200d3..268510d5 100644 --- a/api/src/test/java/com/google/common/flogger/StatAggregatorTest.java +++ b/api/src/test/java/com/google/common/flogger/StatAggregatorTest.java @@ -32,10 +32,10 @@ @RunWith(JUnit4.class) public class StatAggregatorTest { - private StatAggregator create(String name, int capacity, FakeLoggerBackend backend){ + private StatAggregator create(String name, int capacity, FakeLoggerBackend backend) { FluentAggregatedLogger logger = new FluentAggregatedLogger(backend); LogSite logSite = checkNotNull(Platform.getCallerFinder().findLogSite(FluentAggregatedLogger.class, 0), - "logger backend must not return a null LogSite"); + "logger backend must not return a null LogSite"); ScheduledExecutorService pool = Executors.newScheduledThreadPool(1); return new StatAggregator(name, logger, logSite, pool, capacity); @@ -50,13 +50,13 @@ public void testWithSampleRate() { try { statAggregator = statAggregator.withSampleRate(-1); fail("expected IllegalArgumentException"); - } catch (IllegalArgumentException e){ + } catch (IllegalArgumentException e) { } try { statAggregator = statAggregator.withSampleRate(0); fail("expected IllegalArgumentException"); - } catch (IllegalArgumentException e){ + } catch (IllegalArgumentException e) { } statAggregator = statAggregator.withSampleRate(1); @@ -87,7 +87,7 @@ public void testWithSampleRate() { try { statAggregator = statAggregator.withSampleRate(1001); fail("expected IllegalArgumentException"); - } catch (IllegalArgumentException e){ + } catch (IllegalArgumentException e) { } } @@ -148,12 +148,12 @@ public void testAdd() throws InterruptedException { // Test value list is full statAggregator = statAggregator.withNumberWindow(4); - for(int i = 0; i < 5; i++) { + for (int i = 0; i < 5; i++) { statAggregator.add(10); } Thread.sleep(10); //wait for async flush finished. String message = "test\n" + - "min:10, max:10, total:40, count:4, avg:10.0."; + "min:10, max:10, total:40, count:4, avg:10.0."; backend.assertLogged(0).hasMessage(message); // Test flush based on number window @@ -163,7 +163,7 @@ public void testAdd() throws InterruptedException { Thread.sleep(10); // Wait for async flush finished. message = "test\n" + - "min:10, max:10, total:30, count:3, avg:10.0."; + "min:10, max:10, total:30, count:3, avg:10.0."; backend.assertLogged(1).hasMessage(message); } } From 80b7b3b0432388eaebcfa87150090afff17f9d3e Mon Sep 17 00:00:00 2001 From: wallace Date: Thu, 7 Jan 2021 18:23:51 +0800 Subject: [PATCH 8/8] fix unit test bug --- .../common/flogger/backend/system/SimpleBackendLoggerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/test/java/com/google/common/flogger/backend/system/SimpleBackendLoggerTest.java b/api/src/test/java/com/google/common/flogger/backend/system/SimpleBackendLoggerTest.java index 3586eea9..9b5844ea 100644 --- a/api/src/test/java/com/google/common/flogger/backend/system/SimpleBackendLoggerTest.java +++ b/api/src/test/java/com/google/common/flogger/backend/system/SimpleBackendLoggerTest.java @@ -169,7 +169,7 @@ public void testPrintfDateTime() { backend.log(withPrintfStyle("Seconds=%tS", cal.getTimeInMillis())); logger.assertLogCount(4); - logger.assertLogEntry(0, INFO, "Day=SAT 13, Month=July, Year=1985"); + logger.assertLogEntry(0, INFO, "Day=SAT 13, Month=Jul, Year=1985"); logger.assertLogEntry(1, INFO, "Time=5:20:03 AM"); logger.assertLogEntry(2, INFO, "Sat Jul 13 05:20:03 GMT 1985 "); // padded logger.assertLogEntry(3, INFO, "Seconds=03");