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

openmrs / openmrs-core / 26982556974

04 Jun 2026 10:08PM UTC coverage: 63.677% (+0.3%) from 63.427%
26982556974

push

github

ibacher
TRUNK-6392: Improvements to the OpenMRS Logging plugin (#6104)

151 of 195 new or added lines in 8 files covered. (77.44%)

12 existing lines in 5 files now uncovered.

23830 of 37423 relevant lines covered (63.68%)

0.64 hits per line

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

64.29
/api/src/main/java/org/openmrs/logging/OpenmrsConfigurationFactory.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.io.File;
13
import java.net.URI;
14
import java.util.ArrayList;
15
import java.util.Arrays;
16
import java.util.Comparator;
17
import java.util.List;
18
import java.util.Locale;
19

20
import org.apache.commons.io.FileUtils;
21
import org.apache.commons.io.FilenameUtils;
22
import org.apache.commons.lang3.StringUtils;
23
import org.apache.logging.log4j.Level;
24
import org.apache.logging.log4j.core.LoggerContext;
25
import org.apache.logging.log4j.core.config.AbstractConfiguration;
26
import org.apache.logging.log4j.core.config.Configuration;
27
import org.apache.logging.log4j.core.config.ConfigurationFactory;
28
import org.apache.logging.log4j.core.config.ConfigurationSource;
29
import org.apache.logging.log4j.core.config.LoggerConfig;
30
import org.apache.logging.log4j.core.config.Order;
31
import org.apache.logging.log4j.core.config.composite.CompositeConfiguration;
32
import org.apache.logging.log4j.core.config.json.JsonConfiguration;
33
import org.apache.logging.log4j.core.config.plugins.Plugin;
34
import org.apache.logging.log4j.core.config.xml.XmlConfiguration;
35
import org.apache.logging.log4j.core.config.yaml.YamlConfiguration;
36
import org.apache.logging.log4j.status.StatusLogger;
37
import org.openmrs.api.AdministrationService;
38
import org.openmrs.api.ServiceNotFoundException;
39
import org.openmrs.api.context.Context;
40
import org.openmrs.util.ConfigUtil;
41
import org.openmrs.util.OpenmrsConstants;
42
import org.openmrs.util.OpenmrsUtil;
43
import org.openmrs.util.PrivilegeConstants;
44
import org.slf4j.LoggerFactory;
45

46
import static org.openmrs.logging.OpenmrsLoggingUtil.stringToLevel;
47

48
/**
49
 * {@link ConfigurationFactory} to handle OpenMRS's logging configuration.
50
 * <p/>
51
 * Functionality provided by this {@link ConfigurationFactory}:
52
 * <ul>
53
 *     <li>Load log4j2 configuration files from the OpenMRS application directory</li>
54
 *     <li>Ensures that the configuration includes the MEMORY_APPENDER to keep log files in memory</li>
55
 *     <li>Allows the <tt>log.level</tt> setting to override logger settings</li>
56
 * </ul>
57
 */
58
@Plugin(name = "OpenmrsConfigurationFactory", category = ConfigurationFactory.CATEGORY)
59
@Order(10)
60
@SuppressWarnings("unused")
61
public class OpenmrsConfigurationFactory extends ConfigurationFactory {
1✔
62
        
63
        private static final org.slf4j.Logger log = LoggerFactory.getLogger(OpenmrsConfigurationFactory.class);
1✔
64
        
65
        public static final String[] SUFFIXES = new String[] { ".xml", ".yml", ".yaml", ".json", "*" };
1✔
66

67
        // Extensions are all SUFFIXES except wildcard, with leading "." removed
68
        public static final String[] EXTENSIONS = Arrays.stream(SUFFIXES)
1✔
69
                .filter(s -> !s.equals("*")).map(s -> s.substring(1)).toArray(String[]::new);
1✔
70

71
        @Override
72
        public Configuration getConfiguration(LoggerContext loggerContext, String name, URI configLocation) {
73
                if (!isActive()) {
1✔
74
                        return null;
×
75
                }
76

77
                // try to load the configuration from the application data directory
78
                if (configLocation == null) {
1✔
79
                        List<File> configurationFiles = getConfigurationFiles();
1✔
80
                        if (!configurationFiles.isEmpty()) {
1✔
81
                                if (configurationFiles.size() == 1) {
1✔
NEW
82
                                        StatusLogger.getLogger().info("Adding log4j2 configuration file: {}",
×
NEW
83
                                            configurationFiles.get(0).getPath());
×
UNCOV
84
                                        return super.getConfiguration(loggerContext, name, configurationFiles.get(0).toURI());
×
85
                                }
86
                                else {
87
                                        List<AbstractConfiguration> abstractConfigurations = new ArrayList<>();
1✔
88
                                        for (File configFile : configurationFiles) {
1✔
89
                                                Configuration configuration = super.getConfiguration(loggerContext, name, configFile.toURI());
1✔
90
                                                if (configuration instanceof AbstractConfiguration) {
1✔
91
                                                        StatusLogger.getLogger().info("Adding log4j2 configuration file: {}", configFile.getPath());
1✔
92
                                                        abstractConfigurations.add((AbstractConfiguration) configuration);
1✔
93
                                                } else {
NEW
94
                                                        StatusLogger.getLogger().error("Unable to add log4j2 configuration file: {}",
×
NEW
95
                                                            configFile.getPath());
×
96
                                                }
97
                                        }
1✔
98
                                        return new OpenmrsCompositeConfiguration(abstractConfigurations);
1✔
99
                                }
100
                        }
101
                }
102

103
                return super.getConfiguration(loggerContext, name, configLocation);
1✔
104
        }
105
        
106
        @Override
107
        public Configuration getConfiguration(LoggerContext loggerContext, ConfigurationSource source) {
108
                if (source != null && source.getLocation() != null) {
1✔
109
                        switch (FilenameUtils.getExtension(source.getLocation()).toLowerCase(Locale.ROOT)) {
1✔
110
                                case "xml":
111
                                        return new OpenmrsXmlConfiguration(loggerContext, source);
1✔
112
                                case "yaml":
113
                                case "yml":
114
                                        return new OpenmrsYamlConfiguration(loggerContext, source);
×
115
                                case "json":
116
                                        return new OpenmrsJsonConfiguration(loggerContext, source);
×
117
                                default:
118
                                        throw new IllegalArgumentException(
×
119
                                                OpenmrsConfigurationFactory.class.getName() + " does not know how to handle source " + source.getFile());
×
120
                        }
121
                }
122
                return null;
×
123
        }
124
        
125
        @Override
126
        protected String[] getSupportedTypes() {
127
                return SUFFIXES;
1✔
128
        }
129
        
130
        public List<File> getConfigurationFiles() {
131
                List<File> configurationFiles = new ArrayList<>();
1✔
132
                for (File configDir : new File[] {
1✔
133
                        OpenmrsUtil.getDirectoryInApplicationDataDirectory("configuration"),
1✔
134
                        OpenmrsUtil.getApplicationDataDirectoryAsFile()
1✔
135
                }) {
136
                        for (File configFile : FileUtils.listFiles(configDir, EXTENSIONS, false)) {
1✔
137
                                if (configFile.getName().startsWith(getDefaultPrefix()) && configFile.canRead()) {
1✔
138
                                        configurationFiles.add(configFile);
1✔
139
                                }
140
                        }
1✔
141
                }
142
                configurationFiles.sort(Comparator.comparing(File::getName));
1✔
143
                return configurationFiles;
1✔
144
        }
145
        
146
        protected static void doOpenmrsCustomisations(AbstractConfiguration configuration) {
147
                // if we don't have an in-memory appender, add it
148
                MemoryAppender memoryAppender = configuration.getAppender(OpenmrsConstants.MEMORY_APPENDER_NAME);
1✔
149
                if (memoryAppender == null) {
1✔
150
                        memoryAppender = MemoryAppender.newBuilder().build();
1✔
151
                        configuration.addAppender(memoryAppender);
1✔
152
                }
153
                
154
                LoggerConfig rootLogger = configuration.getRootLogger();
1✔
155
                if (rootLogger.getAppenders().get(OpenmrsConstants.MEMORY_APPENDER_NAME) == null) {
1✔
156
                        rootLogger.addAppender(memoryAppender, null, memoryAppender.getFilter());
1✔
157
                }
158

159
                // Check system and runtime properties first — these do not require a session
160
                String logLevel = ConfigUtil.getSystemProperty(OpenmrsConstants.GLOBAL_PROPERTY_LOG_LEVEL);
1✔
161
                if (logLevel == null) {
1✔
162
                        logLevel = ConfigUtil.getRuntimeProperty(OpenmrsConstants.GLOBAL_PROPERTY_LOG_LEVEL);
1✔
163
                }
164

165
                if (logLevel != null) {
1✔
166
                        applyLogLevels(configuration, logLevel);
1✔
167
                } else if (Context.isSessionOpen()) {
1✔
168
                        try {
NEW
169
                                applyLogLevels(configuration);
×
NEW
170
                        } catch (ServiceNotFoundException e) {
×
171
                                // if AdministrationService is not available, we'll assume we're starting up and everything is ok
NEW
172
                                if (!AdministrationService.class.isAssignableFrom(e.getServiceClass())) {
×
NEW
173
                                        throw e;
×
174
                                } else {
NEW
175
                                        StatusLogger.getLogger()
×
NEW
176
                                                .debug("AdministrationService is not yet available; skipping log-level overrides");
×
177
                                }
178
                        }
×
179
                }
180
        }
1✔
181

182
        private static void applyLogLevels(AbstractConfiguration configuration) {
183
                String logLevel;
NEW
184
                Context.addProxyPrivilege(PrivilegeConstants.GET_GLOBAL_PROPERTIES);
×
185
                try {
NEW
186
                        logLevel = ConfigUtil.getGlobalProperty(OpenmrsConstants.GLOBAL_PROPERTY_LOG_LEVEL);
×
187
                } finally {
NEW
188
                        Context.removeProxyPrivilege(PrivilegeConstants.GET_GLOBAL_PROPERTIES);
×
189
                }
190

NEW
191
                if (logLevel != null) {
×
NEW
192
                        applyLogLevels(configuration, logLevel);
×
193
                }
NEW
194
        }
×
195

196
        private static void applyLogLevels(AbstractConfiguration configuration, String logLevel) {
197
                for (String level : logLevel.split(",")) {
1✔
198
                        String[] classAndLevel = level.split(":", 3);
1✔
199
                        if (classAndLevel.length == 0) {
1✔
200
                                break;
×
201
                        } else if (classAndLevel.length == 1) {
1✔
202
                                applyLogLevel(configuration, OpenmrsConstants.LOG_CLASS_DEFAULT, classAndLevel[0].trim());
×
203
                        } else {
204
                                if (classAndLevel.length > 2) {
1✔
NEW
205
                                        StatusLogger.getLogger().warn(
×
206
                                            "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",
207
                                            level);
208
                                }
209
                                applyLogLevel(configuration, classAndLevel[0].trim(), classAndLevel[1].trim());
1✔
210
                        }
211
                }
212
        }
1✔
213
        
214
        private static void applyLogLevel(AbstractConfiguration configuration, String loggerName, String loggerLevel) {
215
                if (StringUtils.isBlank(loggerLevel)) {
1✔
216
                        return;
×
217
                }
218
                
219
                if (loggerName == null) {
1✔
220
                        loggerName = OpenmrsConstants.LOG_CLASS_DEFAULT;
×
221
                }
222

223
                LoggerConfig loggerConfig = configuration.getLoggerConfig(loggerName);
1✔
224
                Level level = stringToLevel(loggerLevel, loggerName);
1✔
225

226
                if (loggerConfig == null || !loggerConfig.getName().equals(loggerName)) {
1✔
227
                        loggerConfig = new LoggerConfig(loggerName, level, true);
1✔
228
                        configuration.addLogger(loggerName, loggerConfig);
1✔
229
                } else {
NEW
230
                        loggerConfig.setLevel(level);
×
231
                }
232
        }
1✔
233

234
        private static class OpenmrsCompositeConfiguration extends CompositeConfiguration {
235

236
                public OpenmrsCompositeConfiguration(final List<? extends AbstractConfiguration> configurations) {
237
                        super(configurations);
1✔
238
                }
1✔
239

240
                @Override
241
                protected void doConfigure() {
242
                        super.doConfigure();
1✔
243
                        doOpenmrsCustomisations(this);
1✔
244
                }
1✔
245
        }
246
        
247
        private static class OpenmrsXmlConfiguration extends XmlConfiguration {
248
                
249
                public OpenmrsXmlConfiguration(LoggerContext loggerContext, ConfigurationSource configSource) {
250
                        super(loggerContext, configSource);
1✔
251
                }
1✔
252
                
253
                @Override
254
                protected void doConfigure() {
255
                        super.doConfigure();
1✔
256
                        doOpenmrsCustomisations(this);
1✔
257
                }
1✔
258
        }
259
        
260
        private static class OpenmrsYamlConfiguration extends YamlConfiguration {
261
                
262
                public OpenmrsYamlConfiguration(LoggerContext loggerContext, ConfigurationSource configSource) {
263
                        super(loggerContext, configSource);
×
264
                }
×
265
                
266
                @Override
267
                protected void doConfigure() {
268
                        super.doConfigure();
×
269
                        doOpenmrsCustomisations(this);
×
270
                }
×
271
        }
272
        
273
        private static class OpenmrsJsonConfiguration extends JsonConfiguration {
274
                
275
                public OpenmrsJsonConfiguration(LoggerContext loggerContext, ConfigurationSource configSource) {
276
                        super(loggerContext, configSource);
×
277
                }
×
278
                
279
                @Override
280
                protected void doConfigure() {
281
                        super.doConfigure();
×
282
                        doOpenmrsCustomisations(this);
×
283
                }
×
284
        }
285
}
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