blob: a94cc415a40ca43ffb1f564ed74fbaee1f9a60e8 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jena.fuseki.system;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Path;
import org.apache.jena.atlas.io.IO;
import org.apache.jena.atlas.lib.Lib;
import org.apache.jena.atlas.logging.LogCtl;
import org.apache.jena.atlas.logging.LogCtlLog4j2;
import org.apache.jena.fuseki.Fuseki;
/**
* FusekiLogging.
* <p>
* This applies to Fuseki run from the command line and embedded.
* <p>
* This does not apply to Fuseki running in Tomcat where it uses the
* servlet 3.0 mechanism described in
* <a href="https://logging.apache.org/log4j/2.x/manual/webapp.html">Log4j2 manual
* (webapp)</a>. See {@code FusekiServerEnvironmentInit}.
*/
public class FusekiLogging
{
// This class must not have static constants, or otherwise not "Fuseki.*"
// or any class else where that might kick off logging. Otherwise, the
// setLogging is pointless (it's already set).
// Set logging.
// 1/ Use system property log4j2.configurationFile if defined.
// 2/ Use file:log4j2.properties if exists
// 3/ Use log4j2.properties on the classpath.
// 4/ Use org/apache/jena/fuseki/log4j2.properties on the classpath.
// 5/ Use built in string
/**
* Places for the log4j properties file at (3).
* This is not the standard, fixed classpath names used by log4j.
* log4j2.properties, log4j2.yaml, log4j2.json, log4j2.xml
*/
private static final String[] resourcesForLog4jProperties = {
"log4j2.properties"
};
public static String envLogLoggingProperty = "FUSEKI_LOGLOGGING";
public static String logLoggingProperty = "fuseki.logLogging";
private static String logLoggingPropertyAlt = "fuseki.loglogging";
// This is also set every call of seLogging.
// That picks up any in-code settings of the logging properties.
private static boolean logLogging = getLogLogging();
private static final boolean getLogLogging() {
String x = System.getProperty(logLoggingPropertyAlt);
if ( x != null ) {
logLogging("Old system property used '%s'", logLoggingPropertyAlt);
return x.equalsIgnoreCase("true");
}
x = Lib.getenv("FUSEKI_LOGLOGGING", logLoggingProperty);
return x != null && x.equalsIgnoreCase("true");
}
private static boolean loggingInitialized = false;
/**
* Mark whether logging is considered "initialized". Some external factor (e.g.
* log4j2 webapp context-param "log4jConfiguration") may mean logging will be
* initialized some other way.
* <p>
* Call this with argument false if the code wants to re-initialize the logging
* otherwise calls of {@code setLogging} will be no-ops.
*/
public static synchronized void markInitialized(boolean isInitialized) {
logLogging("markInitialized("+isInitialized+")");
loggingInitialized = isInitialized;
}
/** Set up logging. */
public static synchronized void setLogging() {
setLogging(null);
}
public static final String log4j2_configurationFile = LogCtl.log4j2ConfigFileProperty;
private static final String log4j2_configurationFileLegacy = LogCtl.log4j2ConfigFilePropertyLegacy;
// Only used by the webapp.
public static final String log4j2_web_configuration = "log4jConfiguration";
public static synchronized boolean hasInitialized() {
return loggingInitialized;
}
/**
* Set up logging. Allow an extra location. This may be null.
*
* @param extraDir
*/
public static synchronized void setLogging(Path extraDir) {
// Cope with repeated calls so code can call this to ensure
if ( loggingInitialized )
return;
loggingInitialized = true;
logLogging = getLogLogging();
logLogging("Set logging");
// Is there a log4j setup provided?
if ( checkSystemProperties(log4j2_configurationFile) ||
checkSystemProperties(log4j2_configurationFileLegacy) ||
System.getenv("LOG4J_CONFIGURATION_FILE") != null )
{
logLogging("External log4j2 setup ");
return ;
}
logLogging("Setup");
// Look for a log4j2.properties file in the current working directory
// and a place (e.g. FUSEKI_BASE in the webapp/full server) for easy customization.
String fn1 = "log4j2.properties";
String fn2 = null;
if ( extraDir != null )
fn2 = extraDir.resolve("log4j2.properties").toString();
if ( attempt(fn1) ) return;
if ( attempt(fn2) ) return;
// Try classpath
for ( String resourceName : resourcesForLog4jProperties ) {
// In log4j2, classpath initialization is fixed name :
// log4j2.properties, log4j2.yaml, log4j2.json, log4j2.xml
// Instead, we manually load a resource.
logLogging("Try classpath %s", resourceName);
URL url = Thread.currentThread().getContextClassLoader().getResource(resourceName);
// if ( url != null ) {
// // Problem - test classes can be on the classpath (development mainly).
// if ( url.toString().contains("-tests.jar") || url.toString().contains("test-classes") )
// url = null;
// }
if ( url != null ) {
try ( InputStream inputStream = url.openStream() ) {
String x = IO.readWholeFileAsUTF8(inputStream);
} catch (IOException ex) { IO.exception(ex); }
try ( InputStream inputStream = url.openStream() ) {
loadConfiguration(inputStream, resourceName);
} catch (IOException ex) { IO.exception(ex); }
logLogging("Found via classpath %s", url);
System.setProperty(log4j2_configurationFile, url.toString());
return;
}
}
// Use built-in.
logLogging("Fallback built-in log4j2setup");
String dftLog4j = log4j2setupFallback();
resetLogging(dftLog4j);
// Stop anything attempting to do it again.
System.setProperty(log4j2_configurationFile, "set");
}
private static boolean checkSystemProperties(String... properties) {
String x = null;
for ( String propertyName : properties ) {
x = System.getProperty(propertyName, null);
if ( x != null ) {
if ( "set".equals(x) ) {
Fuseki.serverLog.warn("Fuseki logging: Unexpected: Log4j2 was setup by some other part of Jena");
return true;
}
return true;
}
}
return false;
}
private static void loadConfiguration(InputStream inputStream, String resourceName) throws IOException {
LogCtlLog4j2.resetLogging(inputStream, resourceName);
}
private static boolean attempt(String fn) {
if ( fn == null )
return false;
try {
logLogging("Try file:"+fn);
File f = new File(fn);
if ( f.exists() ) {
logLogging("Found via file "+fn);
try (InputStream input = new FileInputStream(fn) ) {
loadConfiguration(input, fn);
} catch (IOException ex) { IO.exception(ex); }
System.setProperty(log4j2_configurationFile, "file:" + fn);
return true;
}
}
catch (Throwable th) {}
return false;
}
private static void logLogging(String fmt, Object ... args) {
if ( logLogging ) {
System.err.print("Fuseki Logging: ");
System.err.printf(fmt, args);
System.err.println();
}
}
private static String log4j2setupFallback() {
// The logging file for Fuseki in Tomcat webapp is in "log4j2.properties" in the webapp root directory.
// This is used by command line Fuseki (full and main)
// filters = threshold
// filter.threshold.type = ThresholdFilter
// filter.threshold.level = ALL
return """
## Plain output to stdout
status = error
name = FusekiLogging
appender.console.type = Console
appender.console.name = OUT
appender.console.target = SYSTEM_OUT
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = [%d{yyyy-MM-dd HH:mm:ss}] %-10c{1} %-5p %m%n
rootLogger.level = WARN
rootLogger.appenderRef.stdout.ref = OUT
logger.jena.name = org.apache.jena
logger.jena.level = INFO
logger.arq-exec.name = org.apache.jena.arq.exec
logger.arq-exec.level = INFO
logger.riot.name = org.apache.jena.riot
logger.riot.level = INFO
logger.fuseki.name = org.apache.jena.fuseki
logger.fuseki.level = INFO
logger.fuseki-fuseki.name = org.apache.jena.fuseki.Fuseki
logger.fuseki-fuseki.level = INFO
logger.fuseki-server.name = org.apache.jena.fuseki.Server
logger.fuseki-server.level = INFO
logger.fuseki-config.name = org.apache.jena.fuseki.Config
logger.fuseki-config.level = INFO
logger.fuseki-admin.name = org.apache.jena.fuseki.Admin
logger.fuseki-admin.level = INFO
logger.jetty.name = org.eclipse.jetty
logger.jetty.level = WARN
logger.apache-http.name = org.apache.http
logger.apache-http.level = WARN
logger.shiro.name = org.apache.shiro
logger.shiro.level = WARN
# Hide bug in Shiro 1.5.0
logger.shiro-realm.name = org.apache.shiro.realm.text.IniRealm
logger.shiro-realm.level = ERROR
# This goes out in NCSA format
appender.plain.type = Console
appender.plain.name = PLAIN
appender.plain.layout.type = PatternLayout
appender.plain.layout.pattern = %m%n
logger.fuseki-request.name = org.apache.jena.fuseki.Request
logger.fuseki-request.additivity = false
logger.fuseki-request.level = OFF
logger.fuseki-request.appenderRef.plain.ref = PLAIN
);
""";
}
public static void resetLogging(String configString) {
LogCtlLog4j2.resetLogging(configString);
}
}