1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2002, 2014 Oracle and/or its affiliates.  All rights reserved.
5  *
6  */
7 
8 package com.sleepycat.je.utilint;
9 
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.assertFalse;
12 import static org.junit.Assert.assertNull;
13 import static org.junit.Assert.assertTrue;
14 
15 import java.io.BufferedReader;
16 import java.io.BufferedWriter;
17 import java.io.File;
18 import java.io.FileReader;
19 import java.io.FileWriter;
20 import java.io.PrintWriter;
21 import java.util.ArrayList;
22 import java.util.Enumeration;
23 import java.util.List;
24 import java.util.logging.Handler;
25 import java.util.logging.Level;
26 import java.util.logging.LogManager;
27 import java.util.logging.LogRecord;
28 import java.util.logging.Logger;
29 
30 import org.junit.Test;
31 
32 import com.sleepycat.je.DatabaseException;
33 import com.sleepycat.je.DbInternal;
34 import com.sleepycat.je.Environment;
35 import com.sleepycat.je.EnvironmentConfig;
36 import com.sleepycat.je.EnvironmentMutableConfig;
37 import com.sleepycat.je.dbi.EnvironmentImpl;
38 import com.sleepycat.je.junit.JUnitProcessThread;
39 import com.sleepycat.je.util.TestUtils;
40 import com.sleepycat.util.test.SharedTestUtils;
41 import com.sleepycat.util.test.TestBase;
42 
43 /**
44  * A unit test for testing JE logging programatically.
45  */
46 public class LoggerUtilsTest extends TestBase {
47 
48     /**
49      * If a logging config file is specified, this test cannot expect the
50      * logging properties to have default settings.
51      */
52     private static final boolean DEFAULT_LOGGING_PROPERTIES =
53         System.getProperty("java.util.logging.config.file", "").equals("");
54 
55     private final File envHome;
56     private static final String loggerPrefix = "com.sleepycat.je.";
57     /* Logging configure properties file name. */
58     private static final String fileName = "logging.properties";
59     /* Logging settings in the properties file. */
60     private static final String consoleLevel =
61         "com.sleepycat.je.util.ConsoleHandler.level=INFO";
62     private static final String fileLevel =
63         "com.sleepycat.je.util.FileHandler.level=WARNING";
64 
LoggerUtilsTest()65     public LoggerUtilsTest() {
66         envHome = SharedTestUtils.getTestDir();
67     }
68 
69     /*
70      * Remove the named files from the environment directory.
71      */
removeFiles(File envDir, String name)72     private void removeFiles(File envDir, String name) {
73         File[] files = envDir.listFiles();
74         for (File file : files) {
75             if (file.getName().contains(name)) {
76                 assertTrue("couldn't delete " + name + "for " + envDir,
77                            file.delete());
78             }
79         }
80     }
81 
82     /*
83      * Test whether a JE logger's level can be set programmatically.
84      */
85     @Test
testLoggerLevelsRWEnv()86     public void testLoggerLevelsRWEnv()
87         throws Exception {
88 
89         changeLoggerLevels(false /* readOnly */);
90     }
91 
92     /*
93      * Test whether a JE logger's level can be set programmatically, and that
94      * logging works in a read only environment.
95      */
96     @Test
testLoggerLevelsROEnv()97     public void testLoggerLevelsROEnv()
98         throws Exception {
99 
100         changeLoggerLevels(true /* readOnly */);
101     }
102 
changeLoggerLevels(boolean readOnlyEnv)103     private void changeLoggerLevels(boolean readOnlyEnv)
104         throws Exception {
105 
106         /*
107          * Set the parent's level to OFF, so all logging messages shouldn't be
108          * written to je.info files.
109          */
110         Logger parent = Logger.getLogger("com.sleepycat.je");
111         parent.setLevel(Level.OFF);
112 
113         EnvironmentConfig envConfig = new EnvironmentConfig();
114         envConfig.setAllowCreate(true);
115         envConfig.setConfigParam(EnvironmentConfig.FILE_LOGGING_LEVEL, "ALL");
116         envConfig.setReadOnly(readOnlyEnv);
117         Environment env = new Environment(envHome, envConfig);
118 
119         /* Init a test messages list. */
120         ArrayList<String> messages = new ArrayList<String>();
121         messages.add("Hello, Linda!");
122         messages.add("Hello, Sam!");
123         messages.add("Hello, Charlie!");
124         messages.add("Hello, Mark!");
125         messages.add("Hello, Tao!");
126         messages.add("Hello, Eric!");
127 
128         /* Check the logger level before reset. */
129         checkLoggerLevel();
130 
131         /* Log the test messages. */
132         logMsg(DbInternal.getEnvironmentImpl(env), messages);
133 
134         /*
135          * The loggers were turned off with a level setting of OFF, so there is
136          * should be nothing in the je.info files.
137          */
138         ArrayList<String> readMsgs = readFromInfoFile(readOnlyEnv);
139         assertTrue(readMsgs.size() == 0);
140 
141         /*
142          * Reset the parent level to ALL, so that all logging messages should
143          * be logged.
144          */
145         parent.setLevel(Level.ALL);
146 
147         /* Log the test messages. */
148         logMsg(DbInternal.getEnvironmentImpl(env), messages);
149 
150         /* Check that each test message is in the je.info.0 file. */
151         readMsgs = readFromInfoFile(readOnlyEnv);
152 
153         /*
154          * Since JE logger's level has been set to ALL, additional JE logging
155          * messages from normal operations may also be written to je.info
156          * files. We should check that the actual messages in je.info should be
157          * equal to or larger than the size we directly log.
158          */
159         if (readOnlyEnv) {
160             /* Read only Environment won't log anything to JE FileHandler. */
161             assertTrue(readMsgs.size() == 0);
162         } else {
163 
164             /*
165              * Since JE logger's level has been set to ALL, additional JE
166              * logging messages from normal operations may also be written to
167              * je.info files. We should check that the actual messages in
168              * je.info files should be equal to or larger than the size we
169              * directly log.
170              */
171             assertTrue(readMsgs.size() >= messages.size());
172             for (int i = 0; i < messages.size(); i++) {
173                 boolean contained = false;
174                 for (int j = 0; j < readMsgs.size(); j++) {
175                     if (readMsgs.get(j).contains(messages.get(i))) {
176                         contained = true;
177                         break;
178                     }
179                 }
180                 assertTrue(contained);
181             }
182         }
183 
184         /* Check to see that all JE loggers' level are not changed. */
185         checkLoggerLevel();
186 
187         env.close();
188     }
189 
190     /* Check the level for all JE loggers. */
checkLoggerLevel()191     private void checkLoggerLevel() {
192         Enumeration<String> loggerNames =
193             LogManager.getLogManager().getLoggerNames();
194         while (loggerNames.hasMoreElements()) {
195             String loggerName = loggerNames.nextElement();
196             if (loggerName.startsWith(loggerPrefix)) {
197                 Logger logger = Logger.getLogger(loggerName);
198                 assertNull(logger.getLevel());
199             }
200         }
201     }
202 
203     /* Log some messages. */
logMsg(EnvironmentImpl envImpl, ArrayList<String> messages)204     private void logMsg(EnvironmentImpl envImpl, ArrayList<String> messages) {
205         Logger envLogger = envImpl.getLogger();
206         for (String message : messages) {
207             LoggerUtils.info(envLogger, envImpl, message);
208         }
209     }
210 
211     /* Read the contents in the je.info files. */
readFromInfoFile(boolean readOnlyEnv)212     private ArrayList<String> readFromInfoFile(boolean readOnlyEnv)
213         throws Exception {
214 
215         /* Get the file for je.info.0. */
216         File[] files = envHome.listFiles();
217         File infoFile = null;
218         for (File file : files) {
219             if (("je.info.0").equals(file.getName())) {
220                 infoFile = file;
221                 break;
222             }
223         }
224 
225         /* Make sure the file exists. */
226         ArrayList<String> messages = new ArrayList<String>();
227         if (readOnlyEnv) {
228             return messages;
229         }
230 
231         assertTrue(infoFile != null);
232 
233         /* Read the messages from the file. */
234         BufferedReader in = new BufferedReader(new FileReader(infoFile));
235         String message = new String();
236         while ((message = in.readLine()) != null) {
237             messages.add(message);
238         }
239         in.close();
240 
241         return messages;
242     }
243 
244     /*
245      * Test FileHandler and ConsoleHandler level setting.
246      */
247     @Test
testHandlerLevels()248     public void testHandlerLevels()
249         throws Exception {
250 
251         EnvironmentConfig envConfig = new EnvironmentConfig();
252         envConfig.setAllowCreate(true);
253         Environment env = new Environment(envHome, envConfig);
254 
255         /* Check the initial handler level settings. */
256         EnvironmentImpl envImpl = DbInternal.getEnvironmentImpl(env);
257         Level consoleHandlerLevel = envImpl.getConsoleHandler().getLevel();
258         Level fileHandlerLevel = envImpl.getFileHandler().getLevel();
259         if (DEFAULT_LOGGING_PROPERTIES) {
260             assertEquals(Level.OFF, consoleHandlerLevel);
261             assertEquals(Level.INFO, fileHandlerLevel);
262         }
263 
264         env.close();
265 
266         /* Reopen the Environment with params setting. */
267         envConfig.setConfigParam(EnvironmentConfig.CONSOLE_LOGGING_LEVEL,
268                                  "WARNING");
269         envConfig.setConfigParam(EnvironmentConfig.FILE_LOGGING_LEVEL,
270                                  "SEVERE");
271         env = new Environment(envHome, envConfig);
272         envImpl = DbInternal.getEnvironmentImpl(env);
273         Level newConsoleHandlerLevel = envImpl.getConsoleHandler().getLevel();
274         Level newFileHandlerLevel = envImpl.getFileHandler().getLevel();
275         /* Check that the new level are the same as param setting. */
276         assertEquals(Level.WARNING, newConsoleHandlerLevel);
277         assertEquals(Level.SEVERE, newFileHandlerLevel);
278 
279         /* Make sure the levels are different before and after param setting. */
280         if (DEFAULT_LOGGING_PROPERTIES) {
281             assertFalse(consoleHandlerLevel.equals(newConsoleHandlerLevel));
282             assertFalse(fileHandlerLevel.equals(newFileHandlerLevel));
283         }
284 
285         env.close();
286     }
287 
288     /*
289      * Test whether the configurations inside the properties file are set
290      * correctly in JE Environment.
291      */
292     @Test
testPropertiesSetting()293     public void testPropertiesSetting()
294         throws Exception {
295 
296         invokeProcess(false);
297     }
298 
299     /**
300      *  Start the process and check the exit value.
301      *
302      *  @param bothSetting presents whether the logging configuration and JE
303      *                     params are both set on a same Environment.
304      */
invokeProcess(boolean bothSetting)305     private void invokeProcess(boolean bothSetting)
306         throws Exception {
307 
308         /* Create a property file and write configurations into the file. */
309         String propertiesFile = createPropertiesFile();
310 
311         /*
312          * If bothSetting is true, which means we need to set JE params, so
313          * obviously we need to add another two arguments.
314          */
315         String[] envCommand = bothSetting ? new String[8] : new String[6];
316         envCommand[0] = "-Djava.util.logging.config.file=" + propertiesFile;
317         envCommand[1] = "com.sleepycat.je.utilint.LoggerUtilsTest$" +
318                         "PropertiesSettingProcess";
319         envCommand[2] = envHome.getAbsolutePath();
320         envCommand[3] = "INFO";
321         envCommand[4] = "WARNING";
322         envCommand[5] = bothSetting ? "true" : "false";
323         /* JE Param setting. */
324         if (bothSetting) {
325             envCommand[6] = "WARNING";
326             envCommand[7] = "SEVERE";
327         }
328 
329         /* Start a process. */
330         JUnitProcessThread thread =
331             new JUnitProcessThread("PropertiesSettingProcess", envCommand);
332         thread.start();
333 
334         try {
335             thread.finishTest();
336         } catch (Throwable t) {
337             System.err.println(t.toString());
338         }
339 
340         /* We expect that the process exited normally. */
341         assertEquals(0, thread.getExitVal());
342 
343         /* Remove the created property file. */
344         removeFiles(envHome, fileName);
345     }
346 
347     /* Create a properties file for use. */
createPropertiesFile()348     private String createPropertiesFile()
349         throws Exception {
350 
351         String name = envHome.getAbsolutePath() +
352                       System.getProperty("file.separator") + fileName;
353         File file = new File(name);
354         PrintWriter out =
355             new PrintWriter(new BufferedWriter(new FileWriter(file)));
356         out.println(consoleLevel);
357         out.println(fileLevel);
358         out.close();
359 
360         return name;
361     }
362 
363     /*
364      * Test that JE ConsoleHandler and FileHandler get the correct level when
365      * their levels are set both by the java.util.logging properties file and
366      * JE params.
367      *
368      * We want JE params to override the levels set by the standard
369      * properties file.
370      */
371     @Test
testPropertiesAndParamSetting()372     public void testPropertiesAndParamSetting()
373         throws Exception {
374 
375         invokeProcess(true);
376     }
377 
378     /*
379      * Test that handler levels are mutable.
380      */
381     @Test
testMutableConfig()382     public void testMutableConfig()
383         throws Exception {
384 
385         EnvironmentConfig envConfig = new EnvironmentConfig();
386         envConfig.setAllowCreate(true);
387         envConfig.setConfigParam(EnvironmentConfig.CONSOLE_LOGGING_LEVEL,
388                                  "WARNING");
389         envConfig.setConfigParam(EnvironmentConfig.FILE_LOGGING_LEVEL,
390                                  "SEVERE");
391         Environment env = new Environment(envHome, envConfig);
392 
393         /* Check the init handlers' level setting. */
394         EnvironmentImpl envImpl = DbInternal.getEnvironmentImpl(env);
395         Level consoleHandlerLevel = envImpl.getConsoleHandler().getLevel();
396         Level fileHandlerLevel = envImpl.getFileHandler().getLevel();
397         assertEquals(Level.WARNING, consoleHandlerLevel);
398         assertEquals(Level.SEVERE, fileHandlerLevel);
399 
400         /* Change the handler param setting for an open Environment. */
401         EnvironmentMutableConfig mutableConfig = env.getMutableConfig();
402         mutableConfig.setConfigParam(EnvironmentConfig.CONSOLE_LOGGING_LEVEL,
403                                      "SEVERE");
404         mutableConfig.setConfigParam(EnvironmentConfig.FILE_LOGGING_LEVEL,
405                                      "WARNING");
406         env.setMutableConfig(mutableConfig);
407 
408         /* Check the handler's level has changed. */
409         Level newConsoleHandlerLevel = envImpl.getConsoleHandler().getLevel();
410         Level newFileHandlerLevel = envImpl.getFileHandler().getLevel();
411         assertEquals(Level.SEVERE, newConsoleHandlerLevel);
412         assertEquals(Level.WARNING, newFileHandlerLevel);
413         assertTrue(newConsoleHandlerLevel != consoleHandlerLevel);
414         assertTrue(newFileHandlerLevel != fileHandlerLevel);
415 
416         /* Check levels again. */
417         mutableConfig = env.getMutableConfig();
418         env.setMutableConfig(mutableConfig);
419         consoleHandlerLevel = envImpl.getConsoleHandler().getLevel();
420         fileHandlerLevel = envImpl.getFileHandler().getLevel();
421         assertEquals(Level.SEVERE, consoleHandlerLevel);
422         assertEquals(Level.WARNING, fileHandlerLevel);
423         assertTrue(newConsoleHandlerLevel == consoleHandlerLevel);
424         assertTrue(newFileHandlerLevel == fileHandlerLevel);
425 
426         env.close();
427     }
428 
429     /*
430      * A process for starting a JE Environment with properties file or
431      * configured JE params.
432      */
433     static class PropertiesSettingProcess {
434         private final File envHome;
435         /* Handler levels set through properties configuration file. */
436         private final Level propertyConsole;
437         private final Level propertyFile;
438         /* Handler levels set through JE params. */
439         private final Level paramConsole;
440         private final Level paramFile;
441         /* Indicates whether property file and params are both set. */
442         private final boolean bothSetting;
443         private Environment env;
444 
PropertiesSettingProcess(File envHome, Level propertyConsole, Level propertyFile, boolean bothSetting, Level paramConsole, Level paramFile)445         public PropertiesSettingProcess(File envHome,
446                                         Level propertyConsole,
447                                         Level propertyFile,
448                                         boolean bothSetting,
449                                         Level paramConsole,
450                                         Level paramFile) {
451             this.envHome = envHome;
452             this.propertyConsole = propertyConsole;
453             this.propertyFile = propertyFile;
454             this.bothSetting = bothSetting;
455             this.paramConsole = paramConsole;
456             this.paramFile = paramFile;
457         }
458 
459         /* Open a JE Environment. */
openEnv()460         public void openEnv() {
461             try {
462                 EnvironmentConfig envConfig = new EnvironmentConfig();
463                 envConfig.setAllowCreate(true);
464 
465                 /* If bothSetting is true, set the JE params. */
466                 if (bothSetting) {
467                     envConfig.setConfigParam
468                         (EnvironmentConfig.CONSOLE_LOGGING_LEVEL,
469                          paramConsole.toString());
470                     envConfig.setConfigParam
471                         (EnvironmentConfig.FILE_LOGGING_LEVEL,
472                          paramFile.toString());
473                 }
474 
475                 env = new Environment(envHome, envConfig);
476             } catch (DatabaseException e) {
477                 e.printStackTrace();
478                 System.exit(1);
479             }
480         }
481 
482         /* Check the configured levels. */
check()483         public void check() {
484             if (bothSetting) {
485                 doCheck(paramConsole, paramFile);
486             } else {
487                 doCheck(propertyConsole, propertyFile);
488             }
489         }
490 
doCheck(Level cLevel, Level fLevel)491         private void doCheck(Level cLevel, Level fLevel) {
492             try {
493                 EnvironmentImpl envImpl = DbInternal.getEnvironmentImpl(env);
494                 assertTrue
495                     (envImpl.getConsoleHandler().getLevel() == cLevel);
496                 assertTrue(envImpl.getFileHandler().getLevel() == fLevel);
497             } catch (Exception e) {
498                 e.printStackTrace();
499                 System.exit(2);
500             } finally {
501                 env.close();
502             }
503         }
504 
main(String args[])505         public static void main(String args[]) {
506             PropertiesSettingProcess process = null;
507             try {
508                 Level paramConsole = null;
509                 Level paramFile = null;
510                 if (args.length == 6) {
511                     paramConsole = Level.parse(args[4]);
512                     paramFile = Level.parse(args[5]);
513                 }
514 
515                 process = new PropertiesSettingProcess(new File(args[0]),
516                                                        Level.parse(args[1]),
517                                                        Level.parse(args[2]),
518                                                        new Boolean(args[3]),
519                                                        paramConsole,
520                                                        paramFile);
521             } catch (Exception e) {
522                 e.printStackTrace();
523                 System.exit(3);
524             }
525 
526             if (process == null) {
527         	throw new RuntimeException("Process should have been created");
528             }
529             process.openEnv();
530             process.check();
531         }
532     }
533 
534     /**
535      * Set up two environments with configured handlers, and make sure
536      * that they only log records from the appropriate environment.
537      */
538     @Test
testConfiguredHandler()539     public void testConfiguredHandler() {
540         TestInfo infoA = setupMultipleEnvs("A");
541         TestInfo infoB = setupMultipleEnvs("B");
542         int numTestMsgs = 10;
543         try {
544             Logger loggerA = LoggerUtils.getLogger
545                 (com.sleepycat.je.utilint.LoggerUtils.class);
546             Logger loggerB = LoggerUtils.getLogger
547                 (com.sleepycat.je.utilint.LoggerUtils.class);
548 
549             for (int i = 0; i < numTestMsgs; i++) {
550                 LoggerUtils.logMsg(loggerA,
551                                    infoA.envImpl,
552                                    Level.SEVERE, infoA.prefix + i);
553                 LoggerUtils.logMsg(loggerB,
554                                    infoB.envImpl,
555                                    Level.SEVERE, infoB.prefix + i);
556             }
557 
558             infoA.handler.verify(numTestMsgs);
559             infoB.handler.verify(numTestMsgs);
560         } finally {
561             cleanup(infoA.env, infoA.dir);
562             cleanup(infoB.env, infoB.dir);
563         }
564     }
565 
cleanup(Environment env, File envDir)566     private void cleanup(Environment env, File envDir) {
567         env.close();
568         TestUtils.removeLogFiles("2 envs", envDir, false);
569         removeFiles(envDir, "je.info");
570     }
571 
setupMultipleEnvs(String name)572     private TestInfo setupMultipleEnvs(String name) {
573         String testPrefix  = "TEXT" + name;
574         File dir = new File(envHome, name);
575         dir.mkdirs();
576 
577         EnvironmentConfig config = new EnvironmentConfig();
578         config.setAllowCreate(true);
579         TestHandler handler = new TestHandler(testPrefix);
580         handler.setLevel(Level.SEVERE);
581         config.setLoggingHandler(handler);
582         Environment env = new Environment(dir, config);
583 
584         return new TestInfo(env, handler, testPrefix, dir);
585     }
586 
587     private class TestInfo {
588         EnvironmentImpl envImpl;
589         Environment env;
590         String prefix;
591         TestHandler handler;
592         File dir;
593 
TestInfo(Environment env, TestHandler handler, String testPrefix, File dir)594         TestInfo(Environment env, TestHandler handler,
595                  String testPrefix, File dir) {
596             this.env = env;
597             this.envImpl = DbInternal.getEnvironmentImpl(env);
598             this.handler = handler;
599             this.prefix = testPrefix;
600             this.dir = dir;
601         }
602     }
603 
604     private class TestHandler extends Handler {
605 
606         private final String prefix;
607         private final List<String> logged;
608 
TestHandler(String prefix)609         TestHandler(String prefix) {
610             this.prefix = prefix;
611             logged = new ArrayList<String>();
612         }
613 
verify(int numExpected)614         void verify(int numExpected) {
615             assertEquals(numExpected, logged.size());
616             for (int i = 0; i < numExpected; i++) {
617                 assertEquals(logged.get(i), (prefix + i));
618             }
619         }
620 
621         @Override
publish(LogRecord record)622         public void publish(LogRecord record) {
623             logged.add(record.getMessage());
624         }
625 
626         @Override
flush()627         public void flush() {
628         }
629 
630         @Override
close()631         public void close() throws SecurityException {
632         }
633     }
634 }
635