1 /**
2  * The main module of SLF4D, which publicly imports all of the components that
3  * are typically needed in an application for logging. It also defines the
4  * global shared `loggerFactory` from which all application loggers are
5  * obtained.
6  */
7 module slf4d;
8 
9 public import slf4d.logger;
10 public import slf4d.factory;
11 public import slf4d.handler;
12 public import slf4d.provider;
13 public import slf4d.writer;
14 public import slf4d.level;
15 public import slf4d.log_functions;
16 
17 // Below the public imports, we define some common global state for the SLF4D
18 // logging system.
19 
20 import core.atomic;
21 
22 /** 
23  * The logging provider. It defaults to the default provider included in the
24  * box with SLF4D, but it's intended for applications to change this via the
25  * `configureLoggingProvider` function on application startup.
26  */
27 private shared LoggingProvider loggingProvider;
28 
29 /** 
30  * An internal flag that's set once the logging provider is configured, and
31  * used to issue warnings if the provider is re-configured when it shouldn't be.
32  */
33 private shared bool loggingProviderSet = false;
34 
35 /** 
36  * Configures SLF4D to use the given logging provider. Call this once on
37  * application startup. In order to improve runtime performance, access to this
38  * provider is **not** synchronized, so attempts to reconfigure the logging
39  * provider after startup may lead to undefined behavior.
40  * Params:
41  *   provider = The logging provider to use. If `null` is given then SFL4D will
42  *              use its built-in `NoOpProvider` from `slf4d.noop_provider`.
43  */
44 public void configureLoggingProvider(shared LoggingProvider provider) {
45     if (atomicLoad(loggingProviderSet)) {
46         Logger logger = getLogger();
47         static immutable string fmt = "The SLF4D logging provider has already been " ~
48         "configured with the provider %s. Re-configuring the logging provider " ~
49         "after it has already been configured is not supported, and may have " ~
50         "unintended consequences.";
51         logger.warnF!(fmt)(loggingProvider);
52     }
53     if (provider is null) {
54         import slf4d.noop_provider : NoOpProvider;
55         loggingProvider = new shared NoOpProvider();
56     } else {
57         loggingProvider = provider;
58     }
59     atomicStore(loggingProviderSet, true);
60 }
61 
62 /** 
63  * Gets the global shared logging provider instance. If no provider has been
64  * explicitly configured via `configureLoggingProvider`, then SLF4D's default
65  * provider will be used (or testing provider for unit tests).
66  * Returns: The logging provider.
67  */
68 public shared(LoggingProvider) getLoggingProvider() {
69     if (loggingProvider is null) {
70         import slf4d.default_provider : DefaultProvider;
71         loggingProvider = new DefaultProvider();
72     }
73     return loggingProvider;
74 }
75 
76 /** 
77  * Gets the global shared logger factory instance. If no provider has been
78  * explicitly configured, the `slf4d.default_provider` module's
79  * `DefaultProvider` is used.
80  * Returns: The logger factory.
81  */
82 public shared(LoggerFactory) getLoggerFactory() {
83     return getLoggingProvider().getLoggerFactory();
84 }
85 
86 /** 
87  * Gets a Logger using the configured logger factory. This is a shortcut for
88  * `getLoggerFactory().getLogger()`.
89  * Params:
90  *   name = The name of the logger. Defaults to the name of the module where
91  *          this function is invoked.
92  * Returns: The logger.
93  */
94 public Logger getLogger(string name = __MODULE__) {
95     return getLoggerFactory().getLogger(name);
96 }
97 
98 /** 
99  * Resets the logging state. Call this after your test, if you configured
100  * the global shared logging provider.
101  *
102  * Warning: This function is intended for use in tests, and should NOT be used
103  * in your normal application's logic.
104  */
105 public void resetLoggingState() {
106     loggingProvider = null;
107     atomicStore(loggingProviderSet, false);
108 }
109 
110 unittest {
111     /** 
112      * Asserts that the logging state is not yet initialized.
113      */
114     void assertNotInitialized() {
115         import std.format : format;
116         assert(
117             loggingProvider is null,
118             format!"loggingProvider is not null when it shouldn't have been initialized yet: %s"(loggingProvider)
119         );
120         assert(
121             loggingProviderSet == false,
122             "loggingProviderSet is true the logging provider hasn't been configured yet."
123         );
124     }
125 
126     /** 
127      * Asserts that the logging state has been initialized with a given
128      * provider class.
129      */
130     void assertInitialized(ProviderClass)() {
131         import std.format : format;
132         import std.traits;
133         assert(
134             loggingProvider !is null,
135             "loggingProvider is null when it should have been initialized."
136         );
137         assert(
138             loggingProviderSet,
139             "loggingProviderSet is false when the logging provider should have been initialized."
140         );
141         assert(
142             cast(ProviderClass) loggingProvider,
143             format!"loggingProvider is not of the expected class %s, instead it is %s"(
144                 fullyQualifiedName!ProviderClass,
145                 loggingProvider
146             )
147         );
148     }
149 
150     import slf4d.test;
151     synchronized(loggingTestingMutex) {
152         // Test that a warning is issued if the provider is configured more than once.
153         assertNotInitialized();
154         auto provider = new TestingLoggingProvider();
155         configureLoggingProvider(provider);
156         assert(loggingProviderSet == true, "loggingProviderSet is not true after configuring the logging provider.");
157         // Now try and configure it again. A warning message should be produced.
158         configureLoggingProvider(provider);
159         assertInitialized!TestingLoggingProvider();
160         assert(provider.messageCount == 1 && provider.messageCount(Levels.WARN) == 1);
161         resetLoggingState();
162         assertNotInitialized();
163 
164         // Test that if `null` is given, the NoOpProvider is used.
165         assertNotInitialized();
166         configureLoggingProvider(null);
167         import slf4d.noop_provider : NoOpProvider;
168         assertInitialized!NoOpProvider();
169         resetLoggingState();
170     }
171 }
172 
173 /**
174  * An exception that may be thrown by certain logging features. Within SLF4D,
175  * documentation will be provided to declare whenever a function may throw this
176  * exception.
177  */
178 public class LoggingException : Exception {
179     /**
180      * Constructs this exception with a message and throwable cause.
181      * Params:
182      *   msg = The message to provide.
183      *   next = The throwable that caused this exception.
184      */
185     this(string msg, Throwable next) {
186         super(msg, next);
187     }
188 
189     /**
190      * Constructs this exception with a message.
191      * Params:
192      *   msg = The message to provide.
193      */
194     this(string msg) {
195         super(msg);
196     }
197 }