• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

openmrs / openmrs-core / 27024944531

05 Jun 2026 03:47PM UTC coverage: 65.424% (+0.2%) from 65.186%
27024944531

push

github

ibacher
TRUNK-6392: Follow-up: remove JSpecify annotations

23745 of 36294 relevant lines covered (65.42%)

0.65 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.55
/api/src/main/java/org/openmrs/logging/OpenmrsLoggingUtil.java
1
/**
2
 * This Source Code Form is subject to the terms of the Mozilla Public License,
3
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
4
 * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
5
 * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
6
 *
7
 * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
8
 * graphic logo is a trademark of OpenMRS Inc.
9
 */
10
package org.openmrs.logging;
11

12
import java.nio.file.Paths;
13
import java.util.Locale;
14

15
import org.apache.commons.lang3.StringUtils;
16
import org.apache.logging.log4j.Level;
17
import org.apache.logging.log4j.LogManager;
18
import org.apache.logging.log4j.core.Appender;
19
import org.apache.logging.log4j.core.Logger;
20
import org.apache.logging.log4j.core.LoggerContext;
21
import org.apache.logging.log4j.core.appender.AbstractFileAppender;
22
import org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender;
23
import org.apache.logging.log4j.core.appender.FileAppender;
24
import org.apache.logging.log4j.core.appender.MemoryMappedFileAppender;
25
import org.apache.logging.log4j.core.appender.RandomAccessFileAppender;
26
import org.apache.logging.log4j.core.appender.RollingFileAppender;
27
import org.apache.logging.log4j.core.appender.RollingRandomAccessFileAppender;
28
import org.apache.logging.log4j.core.config.LoggerConfig;
29
import org.apache.logging.log4j.status.StatusLogger;
30
import org.openmrs.annotation.Logging;
31
import org.openmrs.api.ServiceNotFoundException;
32
import org.openmrs.api.context.Context;
33
import org.openmrs.util.ConfigUtil;
34
import org.openmrs.util.OpenmrsConstants;
35
import org.openmrs.util.PrivilegeConstants;
36
import org.slf4j.LoggerFactory;
37

38
/**
39
 * Utility methods related to logging.
40
 * In general, module-level code should likely only call {@link #getMemoryAppender()} and
41
 * {@link #getOpenmrsLogLocation()}, however, the methods to update loggers are also exposed if necessary.
42
 *
43
 * @since 2.4.4, 2.5.1, 2.6.0
44
 */
45
public final class OpenmrsLoggingUtil {
46
        
47
        private static final org.slf4j.Logger log = LoggerFactory.getLogger(OpenmrsLoggingUtil.class);
1✔
48
        
49
        private OpenmrsLoggingUtil() {
50
        }
51
        
52
        /**
53
         * Gets the in-memory log appender. This method needed to be added as it is much more difficult to
54
         * get a specific appender in the Log4J2 architecture. This method is called in places where we need
55
         * to display logging message.
56
         *
57
         * @since 2.4.4, 2.5.1, 2.6.0
58
         */
59
        @Logging(ignore = true)
60
        public static MemoryAppender getMemoryAppender() {
61
                LoggerContext context = getLog4j2Context();
×
62
                if (context == null) {
×
63
                        return null;
×
64
                }
65

66
                MemoryAppender memoryAppender = context.getConfiguration().getAppender(OpenmrsConstants.MEMORY_APPENDER_NAME);
×
67

68
                if (memoryAppender != null && !memoryAppender.isStarted()) {
×
69
                        memoryAppender.start();
×
70
                }
71
                
72
                return memoryAppender;
×
73
        }
74
        
75
        /**
76
         * Returns the location of the OpenMRS log file.
77
         * <p/>
78
         * <strong>Warning:</strong> the result of this call can return null if either the file appender uses a name other than
79
         * {@link OpenmrsConstants#LOG_OPENMRS_FILE_APPENDER} or if the appender with that name is not one of the default log4j2
80
         * file appending types.
81
         *
82
         * @return the path to the OpenMRS log file
83
         * */
84
        public static String getOpenmrsLogLocation() {
85
                LoggerContext context = getLog4j2Context();
1✔
86
                if (context == null) {
1✔
87
                        return null;
×
88
                }
89

90
                Appender fileAppender = context.getConfiguration().getAppender(OpenmrsConstants.LOG_OPENMRS_FILE_APPENDER);
1✔
91

92
                String fileName = null;
1✔
93
                if (fileAppender instanceof AbstractOutputStreamAppender) {
1✔
94
                        if (fileAppender instanceof RollingFileAppender) {
×
95
                                fileName = ((RollingFileAppender) fileAppender).getFileName();
×
96
                        } else if (fileAppender instanceof FileAppender) {
×
97
                                fileName = ((FileAppender) fileAppender).getFileName();
×
98
                        } else if (fileAppender instanceof MemoryMappedFileAppender) {
×
99
                                fileName = ((MemoryMappedFileAppender) fileAppender).getFileName();
×
100
                        } else if (fileAppender instanceof RollingRandomAccessFileAppender) {
×
101
                                fileName = ((RollingRandomAccessFileAppender) fileAppender).getFileName();
×
102
                        } else if (fileAppender instanceof RandomAccessFileAppender) {
×
103
                                fileName = ((RandomAccessFileAppender) fileAppender).getFileName();
×
104
                        } else if (fileAppender instanceof AbstractFileAppender) {
×
105
                                fileName = ((AbstractFileAppender<?>) fileAppender).getFileName();
×
106
                        } else {
107
                                return null;
×
108
                        }
109
                }
110
                
111
                return fileName == null ? null : Paths.get("", fileName).toAbsolutePath().toString();
1✔
112
        }
113
        
114
        /**
115
         * Sets the org.openmrs Log4J logger's level if global property log.level.openmrs (
116
         * OpenmrsConstants.GLOBAL_PROPERTY_LOG_LEVEL ) exists. Valid values for global property are
117
         * trace, debug, info, warn, error or fatal.
118
         */
119
        @Logging(ignore = true)
120
        public static void applyLogLevels() {
121
                applyLogLevels(null);
1✔
122
        }
1✔
123

124
        @Logging(ignore = true)
125
        public static void applyLogLevels(String logLevelGp) {
126
                // Check system and runtime properties first — these do not require a session
127
                String logLevel = ConfigUtil.getSystemProperty(OpenmrsConstants.GLOBAL_PROPERTY_LOG_LEVEL);
1✔
128
                if (logLevel == null) {
1✔
129
                        logLevel = ConfigUtil.getRuntimeProperty(OpenmrsConstants.GLOBAL_PROPERTY_LOG_LEVEL);
1✔
130
                }
131

132
                if (logLevel == null && logLevelGp != null) {
1✔
133
                        logLevel = logLevelGp;
1✔
134
                        // Fall back to global property only if a session is open
135
                } else if (logLevel != null && logLevelGp != null) {
1✔
136
                        StatusLogger.getLogger().info("Ignoring GP value \"{}\" as a system or runtime property is already set",
×
137
                            logLevelGp);
138
                } else if (logLevel == null && Context.isSessionOpen()) {
1✔
139
                        Context.addProxyPrivilege(PrivilegeConstants.GET_GLOBAL_PROPERTIES);
1✔
140
                        try {
141
                                logLevel = ConfigUtil.getGlobalProperty(OpenmrsConstants.GLOBAL_PROPERTY_LOG_LEVEL);
1✔
142
                        } catch (ServiceNotFoundException e) {
×
143
                                StatusLogger.getLogger().error("An exception was thrown while trying to get the {} global property",
×
144
                                    OpenmrsConstants.GLOBAL_PROPERTY_LOG_LEVEL, e);
145
                                return;
×
146
                        } finally {
147
                                Context.removeProxyPrivilege(PrivilegeConstants.GET_GLOBAL_PROPERTIES);
1✔
148
                        }
149
                }
150

151
                if (logLevel == null) {
1✔
152
                        return;
1✔
153
                }
154

155
                synchronized (OpenmrsLoggingUtil.class) {
1✔
156
                        for (String level : logLevel.split(",")) {
1✔
157
                                String[] classAndLevel = level.split(":", 3);
1✔
158
                                if (classAndLevel.length == 0) {
1✔
159
                                        break;
×
160
                                } else if (classAndLevel.length == 1) {
1✔
161
                                        applyLogLevelInternal(OpenmrsConstants.LOG_CLASS_DEFAULT, classAndLevel[0].trim());
1✔
162
                                } else {
163
                                        if (classAndLevel.length > 2) {
1✔
164
                                                StatusLogger.getLogger().warn(
×
165
                                                    "Could not properly parse \"{}\" into a class and level due to too many colons. Expected format is <class>:<level>, e.g., org.openmrs.api:INFO",
166
                                                    level);
167
                                        }
168
                                        applyLogLevelInternal(classAndLevel[0].trim(), classAndLevel[1].trim());
1✔
169
                                }
170
                        }
171
                        
172
                        // DO NOT USE LogManager#getContext() here as this might reset the logger context
173
                        LoggerContext context = getLog4j2Context();
1✔
174
                        if (context != null) {
1✔
175
                                context.updateLoggers();
1✔
176
                        }
177
                }
1✔
178
        }
1✔
179
        
180
        /**
181
         * Set the log4j log level for class <code>logClass</code> to <code>logLevel</code>.
182
         *
183
         * @param logClass optional string giving the class level to change. Defaults to
184
         *                 {@link OpenmrsConstants#LOG_CLASS_DEFAULT} . Should be something like org.openmrs.___
185
         * @param logLevel one of <tt>OpenmrsConstants.LOG_LEVEL_*</tt> constants
186
         */
187
        public static void applyLogLevel(String logClass, String logLevel) {
188
                if (StringUtils.isNotBlank(logLevel)) {
1✔
189
                        synchronized (OpenmrsLoggingUtil.class) {
1✔
190
                                applyLogLevelInternal(logClass, logLevel);
1✔
191

192
                                LoggerContext context = getLog4j2Context();
1✔
193
                                if (context != null) {
1✔
194
                                        context.updateLoggers();
1✔
195
                                }
196
                        }
1✔
197
                }
198
        }
1✔
199
        
200
        /**
201
         * This method is the implementation of applying a level to a logger. It is intended to be called in an
202
         * already synchronized context. Note these changes will only be applied once a call to
203
         * {@link LoggerContext#updateLoggers()} is made.
204
         *
205
         * @param logClass optional string giving the class level to change. Defaults to
206
         *                 *            OpenmrsConstants.LOG_CLASS_DEFAULT . Should be something like org.openmrs.___
207
         * @param logLevel one of OpenmrsConstants.LOG_LEVEL_* constants
208
         */
209
        private static void applyLogLevelInternal(String logClass, String logLevel) {
210
                if (StringUtils.isNotBlank(logLevel)) {
1✔
211
                        // the default log class is org.openmrs.api
212
                        if (StringUtils.isEmpty(logClass)) {
1✔
213
                                logClass = OpenmrsConstants.LOG_CLASS_DEFAULT;
1✔
214
                        }
215
                        
216
                        // DO NOT USE LogManager#getContext() here as this will reset the logger context
217
                        LoggerContext context = getLog4j2Context();
1✔
218
                        if (context == null) {
1✔
219
                                return;
×
220
                        }
221

222
                        LoggerConfig configuration = context.getConfiguration().getLoggerConfig(logClass);
1✔
223
                        Level level = stringToLevel(logLevel, logClass);
1✔
224

225
                        if (configuration == null || !configuration.getName().equals(logClass)) {
1✔
226
                                configuration = new LoggerConfig(logClass, level, true);
1✔
227
                                context.getConfiguration().addLogger(logClass, configuration);
1✔
228
                        } else {
229
                                configuration.setLevel(level);
1✔
230
                        }
231
                }
232
        }
1✔
233
        
234
        /**
235
         * Reloads the logging configuration
236
         */
237
        public static void reloadLoggingConfiguration() {
238
                org.apache.logging.log4j.spi.LoggerContext context = LogManager.getContext(true);
1✔
239
                if (context instanceof LoggerContext) {
1✔
240
                        // The general interface does not guarantee that reconfigure() exists
241
                        ((LoggerContext) context).reconfigure();
1✔
242
                } else {
243
                        StatusLogger.getLogger()
×
244
                                .warn("Unable to reload logging configuration as we are not using the Log4J2 LoggerContext");
×
245
                }
246
        }
1✔
247

248
        /**
249
         * Takes a string representing a log level and returns the corresponding {@code Level} object.
250
         * <p/>
251
         * Used to support user-supplied log-levels and backwards compatibility with the Log4J1 API.
252
         *
253
         * @param logLevel The string level to convert
254
         * @return The corresponding {@code Level} or {@code Level#WARN} if unknown
255
         */
256
        public static Level stringToLevel(String logLevel) {
257
                return stringToLevel(logLevel, null);
1✔
258
        }
259

260
        /**
261
         * Takes a string representing a log level and returns the corresponding {@code Level} object.
262
         * <p/>
263
         * Used to support user-supplied log-levels and backwards compatibility with the Log4J1 API.
264
         *
265
         * @param logLevel The string level to convert
266
         * @param logClass The class for this logger. Used to vary the default for the default logger.
267
         * @return The corresponding {@code Level} or {@code Level#WARN} if unknown
268
         * @see OpenmrsConstants#LOG_CLASS_DEFAULT
269
         */
270
        public static Level stringToLevel(String logLevel, String logClass) {
271
                Level level;
272
                if (logLevel == null) {
1✔
273
                        return Level.WARN;
1✔
274
                }
275

276
                logLevel = logLevel.toLowerCase(Locale.ROOT);
1✔
277
                switch (logLevel) {
1✔
278
                        case OpenmrsConstants.LOG_LEVEL_TRACE:
279
                                level = Level.TRACE;
1✔
280
                                break;
1✔
281
                        case OpenmrsConstants.LOG_LEVEL_DEBUG:
282
                                level = Level.DEBUG;
1✔
283
                                break;
1✔
284
                        case OpenmrsConstants.LOG_LEVEL_INFO:
285
                                level = Level.INFO;
1✔
286
                                break;
1✔
287
                        case OpenmrsConstants.LOG_LEVEL_WARN:
288
                                level = Level.WARN;
1✔
289
                                break;
1✔
290
                        case OpenmrsConstants.LOG_LEVEL_ERROR:
291
                                level = Level.ERROR;
1✔
292
                                break;
1✔
293
                        case OpenmrsConstants.LOG_LEVEL_FATAL:
294
                                level = Level.FATAL;
1✔
295
                                break;
1✔
296
                        default:
297
                                log.warn("Log level {} is invalid. " + "Valid values are trace, debug, info, warn, error or fatal",
1✔
298
                                    logLevel);
299
                                if (logClass != null && logClass.equals(OpenmrsConstants.LOG_CLASS_DEFAULT)) {
1✔
300
                                        level = Level.INFO;
1✔
301
                                } else {
302
                                        level = Level.WARN;
1✔
303
                                }
304
                                break;
305
                }
306

307
                return level;
1✔
308
        }
309

310
        private static LoggerContext getLog4j2Context() {
311
                // DO NOT USE LogManager#getContext() here as this will reset the logger context
312
                org.apache.logging.log4j.Logger rootLogger = LogManager.getRootLogger();
1✔
313
                if (!(rootLogger instanceof Logger)) {
1✔
314
                        StatusLogger.getLogger()
×
315
                                .error("Could not get the LoggerContext as this configuration is not using the standard Log4J2 context");
×
316
                        return null;
×
317
                }
318

319
                return ((Logger) rootLogger).getContext();
1✔
320
        }
321
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc