differentContext = new HashMap<>();
344 | differentContext.put("foo", "baz");
345 |
346 | DiagnosticContextSupplier supplier = () -> context;
347 | DiagnosticContextSupplier differentSupplier = () -> differentContext;
348 |
349 | assertNotSame(logger.with(supplier), logger.with(differentSupplier));
350 | }
351 | //endregion
352 |
353 | @Test
354 | public void close_shouldThrowAnException() throws Exception {
355 | /*
356 | Application code shouldn't normally close a real logger, so throw an Exception in the test double
357 | to discourage it
358 | */
359 | try {
360 | logger.close();
361 | fail("Expected an exception");
362 | } catch (IllegalStateException e) {
363 | assertThat(e.getMessage(), containsString("OpsLogger instances should not be closed by application code."));
364 | }
365 | }
366 |
367 | enum TestMessages implements LogMessage {
368 | Foo("CODE-Foo", "No Fields"),
369 | Bar("CODE-Bar", "One Field: %s"),
370 | BadFormatString("CODE-BFS", "%++d"),
371 | InvalidNullCode(null, "Blah"),
372 | InvalidEmptyCode("", "Blah"),
373 | InvalidNullFormat("CODE-InvalidNullFormat", null),
374 | InvalidEmptyFormat("CODE-InvalidEmptyFormat", ""),
375 | MessageWithMultipleArguments("CODE-MultipleArguments", "Multiple Format String Arguments: %s %s");
376 |
377 | //region LogMessage implementation guts
378 | private final String messageCode;
379 | private final String messagePattern;
380 |
381 | TestMessages(String messageCode, String messagePattern) {
382 | this.messageCode = messageCode;
383 | this.messagePattern = messagePattern;
384 | }
385 |
386 | @Override
387 | public String getMessageCode() {
388 | return messageCode;
389 | }
390 |
391 | @Override
392 | public String getMessagePattern() {
393 | return messagePattern;
394 | }
395 |
396 | //endregion
397 | }
398 | }
399 |
--------------------------------------------------------------------------------
/opslogger/.gitignore:
--------------------------------------------------------------------------------
1 | /test.log
--------------------------------------------------------------------------------
/opslogger/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java'
2 | apply from: rootProject.file("jacoco-support.gradle")
3 |
4 | sourceCompatibility = "1.8"
5 | targetCompatibility = "1.8"
6 |
7 | dependencies {
8 | testCompile junit, hamcrest, mockito
9 | testCompile dagger, daggerCompiler, spring
10 | }
11 |
12 | group = 'com.equalexperts'
13 | archivesBaseName = 'opslogger'
14 | version = rootProject.version
15 |
16 | apply from: rootProject.file("bintray-support.gradle")
17 |
18 | idea.springFacets << 'src/test/resources/applicationContext.xml'
19 |
20 | javadoc {
21 | exclude "com/equalexperts/logging/impl/*"
22 | }
--------------------------------------------------------------------------------
/opslogger/src/main/java/com/equalexperts/logging/DiagnosticContextSupplier.java:
--------------------------------------------------------------------------------
1 | package com.equalexperts.logging;
2 |
3 | import java.util.Map;
4 |
5 | /**
6 | * Provides contextual information for log messages.
7 | *
8 | * This is a set of name value pairs that should be
9 | * added to every log message when available.
10 | * Two common examples of contextual information are the current username
11 | * and the current request id.
12 | *
13 | * Contextual name-value pairs are only added to log messages when the value is not null or empty.
14 | * For example, in the case of username, a username=XXX name value pair would not be added to log messages
15 | * generated by anonymous users, or to background tasks that could not be easily associated with a user.
16 | */
17 | @FunctionalInterface
18 | public interface DiagnosticContextSupplier {
19 | /**
20 | * Provide contextual information about the current log message.
21 | *
22 | * This method should generally return a different map each time it is called.
23 | *
24 | *
A common implementation strategy is to access thread-local information about the current request to build an
25 | * appropriate map of name-value pairs for each individual request.
26 | *
27 | * @return a map of name-value pairs
28 | */
29 | Map getMessageContext();
30 | }
--------------------------------------------------------------------------------
/opslogger/src/main/java/com/equalexperts/logging/LogMessage.java:
--------------------------------------------------------------------------------
1 | package com.equalexperts.logging;
2 |
3 | /**
4 | * To be implemented by enumerations of log messages in applications.
5 | */
6 | public interface LogMessage {
7 | /**
8 | * @return unique code for this particular log message. Example "QA001"
9 | */
10 |
11 | String getMessageCode();
12 |
13 | /**
14 | * @return message pattern for this log message. Example "%d out of %d processed."
15 | */
16 |
17 | String getMessagePattern();
18 | }
19 |
--------------------------------------------------------------------------------
/opslogger/src/main/java/com/equalexperts/logging/OpsLogger.java:
--------------------------------------------------------------------------------
1 | package com.equalexperts.logging;
2 |
3 | import com.equalexperts.logging.impl.ActiveRotationRegistry;
4 |
5 | /**
6 | * OpsLogger is the interface used to log messages by the application. Instances are usually constructed as singletons
7 | * and injected into application classes via the application's usual dependency injection mechanism.
8 | *
9 | * All instances produced by {@link OpsLoggerFactory#build()} are thread-safe, and can be safely
10 | * accessed by multiple threads.
11 | */
12 | public interface OpsLogger & LogMessage> extends AutoCloseable {
13 | /**
14 | * Log message using message.getMessagePattern as the format and details as the format arguments.
15 | * @param message enum to log
16 | * @param details format string arguments to message.getMessagePattern()
17 | */
18 | void log(T message, Object... details);
19 |
20 | /**
21 | * Log message using message.getMessagePattern as the format and details as the format arguments, with
22 | * the processed cause added.
23 | * @param message enum to log
24 | * @param cause stack trace to process and include in the log message
25 | * @param details format string arguments to message.getMessagePattern()
26 | */
27 | void logThrowable(T message, Throwable cause, Object... details);
28 |
29 | /**
30 | * Create a nested logger that uses a local DiagnosticContextSupplier, which is often
31 | * convenient during parallel stream processing.
32 | *
33 | * Note: OpsLogger instances created by calling this method should not be closed,
34 | * and will ignore calls to the close() method.
35 | *
36 | * @param override a supplier for a local diagnostic context
37 | * @return a new OpsLogger instance which will use the provided DiagnosticContextSupplier instead of the global one
38 | */
39 | OpsLogger with(DiagnosticContextSupplier override);
40 |
41 | /**
42 | * Refreshes file handles for all log files, providing active rotation support.
43 | * This method should be called between rotating the original file, and manipulating (archiving, compressing, etc)
44 | * it. The postRotate
block in logRotate is an excellent example of when to use this method.
45 | *
46 | * This method will not return until all writing to old file handles has completed.
47 | *
48 | * Exposing this method via JMX or an administrative API some kind is the intended use case.
49 | */
50 | static void refreshFileHandles() {
51 | ActiveRotationRegistry.getSingletonInstance().refreshFileHandles();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/opslogger/src/main/java/com/equalexperts/logging/OpsLoggerFactory.java:
--------------------------------------------------------------------------------
1 | package com.equalexperts.logging;
2 |
3 | import com.equalexperts.logging.impl.AsyncOpsLoggerFactory;
4 | import com.equalexperts.logging.impl.BasicOpsLoggerFactory;
5 | import com.equalexperts.logging.impl.InfrastructureFactory;
6 |
7 | import java.io.PrintStream;
8 | import java.io.UncheckedIOException;
9 | import java.nio.file.Files;
10 | import java.nio.file.Path;
11 | import java.util.Map;
12 | import java.util.Objects;
13 | import java.util.Optional;
14 | import java.util.function.Consumer;
15 | import java.util.function.Supplier;
16 |
17 | /**
18 | * Constructs OpsLogger
instances.
19 | *
20 | * Instances of this class are not thread-safe, and should not be accessed by multiple
21 | * threads.
22 | *
23 | * @see OpsLogger
24 | */
25 | public class OpsLoggerFactory {
26 |
27 | private Optional loggerOutput = Optional.empty();
28 | private Optional logfilePath = Optional.empty();
29 |
30 | private boolean async = false;
31 | private Optional storeStackTracesInFilesystem = Optional.empty();
32 | private Optional stackTraceStoragePath = Optional.empty();
33 | private Optional> errorHandler = Optional.empty();
34 | private Optional contextSupplier = Optional.empty();
35 |
36 | private Optional> cachedInstance = Optional.empty();
37 |
38 | private AsyncOpsLoggerFactory asyncOpsLoggerFactory = new AsyncOpsLoggerFactory();
39 | private BasicOpsLoggerFactory basicOpsLoggerFactory = new BasicOpsLoggerFactory();
40 |
41 | /**
42 | * The destination for the log strings. A typical value is System.out.
43 | * @param printStream destination
44 | * @return this
for further configuration
45 | */
46 | public OpsLoggerFactory setDestination(PrintStream printStream) {
47 | validateParametersForSetDestination(printStream);
48 | clearCachedInstance();
49 | loggerOutput = Optional.of(printStream);
50 | logfilePath = Optional.empty();
51 | return this;
52 | }
53 |
54 | /**
55 | * The path of the file to print the log strings to. Is closed and reopened frequently to allow outside log rotation to work.
56 | * The path is used as-is.
57 | * @param path path for log file
58 | * @return this
for further configuration
59 | */
60 | public OpsLoggerFactory setPath(Path path) {
61 | validateParametersForSetPath(path);
62 | clearCachedInstance();
63 | logfilePath = Optional.of(path).map(Path::toAbsolutePath);
64 | loggerOutput = Optional.empty();
65 | return this;
66 | }
67 |
68 | /**
69 | * Should stack traces be placed in individual files or printed along with the log statements?
70 | * If called with true, each unique stack trace will placed in its own file. (see setStackTraceStoragePath).
71 | * If called with false, stack traces will be printed to main log file.
72 | * @param store true for separate files, false for inlined in log file
73 | * @return this
for further configuration
74 | */
75 | public OpsLoggerFactory setStoreStackTracesInFilesystem(boolean store) {
76 | clearCachedInstance();
77 | storeStackTracesInFilesystem = Optional.of(store);
78 | if (!store) {
79 | stackTraceStoragePath = Optional.empty();
80 | }
81 | return this;
82 | }
83 |
84 | /**
85 | * Path to directory to contain individual stack trace files.
86 | *
87 | * Must be called with a valid path corresponding to a directory, where stack traces will be stored
88 | * (see setStoreStackTracesInFilesystem). If the directory does not exist, it will be created.
89 | * @param directory valid path for target directory
90 | * @return this
for further configuration
91 | */
92 | public OpsLoggerFactory setStackTraceStoragePath(Path directory) {
93 | validateParametersForSetStackTraceStoragePath(directory);
94 | clearCachedInstance();
95 | setStoreStackTracesInFilesystem(true);
96 | stackTraceStoragePath = Optional.of(directory);
97 | return this;
98 | }
99 |
100 | /**
101 | * Handler for when exceptions occur when logging.
102 | *
103 | * If any exception is thrown "inside" this logger, it will caught and be passed on to this error handler,
104 | * which then is responsible for any further error handling. The log message causing the error, will not
105 | * be processed further.
106 | * @param handler Consumer of Throwables handleling any exception encountered.
107 | * @return this
for further configuration
108 | */
109 | public OpsLoggerFactory setErrorHandler(Consumer handler) {
110 | clearCachedInstance();
111 | errorHandler = Optional.ofNullable(handler);
112 | return this;
113 | }
114 |
115 | /**
116 | * This method will be removed in a future release.
117 | *
118 | * @param supplier the map supplier. (for example: ()->map
)
119 | * @deprecated Replaced by {@link #setGlobalDiagnosticContextSupplier(DiagnosticContextSupplier)}.
120 | * @return this
for further configuration
121 | */
122 | @Deprecated
123 | public OpsLoggerFactory setCorrelationIdSupplier(Supplier