org.elasticsearch.xpack.sql.jdbc.Debug Maven / Gradle / Ivy
Show all versions of galen Show documentation
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
package org.elasticsearch.xpack.sql.jdbc;
import org.elasticsearch.xpack.sql.client.SuppressForbidden;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
/**
* Class handling debug logging. Typically disabled (hence why it's called debug).
* JDBC carries a lot of legacy conventions, logging being one of them - in JDBC logging was expected to
* be to System.Err/Out since there were no logging frameworks at the time.
* This didn't work so the API was changed through {@link DriverManager#getLogStream()} however that also had issues
* being global and not working well with encoding (hence why {@link DriverManager#getLogWriter()} was introduced)
* and was changed again through {@link DataSource#getLogWriter()}.
* However by then the damage was done and most drivers don't use either and have their own logging implementation.
*
* This class tries to cater to both audiences - use the legacy, Writer way if needed though strive to use the
* proper typical approach, that of specifying intention and output (file) in the URL.
*
* For this reason the {@link System#out} and {@link System#err} are being referred in this class though are used only
* when needed.
*/
final class Debug {
// cache for streams created by ourselves
private static final Map OUTPUT_CACHE = new HashMap<>();
// reference counter for a given output
private static final Map OUTPUT_REFS = new HashMap<>();
// cache of loggers that rely on external/managed printers
private static final Map OUTPUT_MANAGED = new HashMap<>();
private static volatile DebugLog ERR = null, OUT = null;
private static volatile PrintStream SYS_ERR = null, SYS_OUT = null;
/**
* Create a proxied Connection which performs logging of all methods being invoked.
* Typically Debug will read its configuration from the configuration and act accordingly however
* there are two cases where the output is specified programmatically, namely through
* {@link DriverManager#setLogWriter(PrintWriter)} and {@link DataSource#setLogWriter(PrintWriter)}.
* The former is the 'legacy' way, having a global impact on all drivers while the latter allows per
* instance configuration.
*
* As both approaches are not widely used, Debug will take the principle of least surprise and pick its
* own configuration first; if that does not exist it will fallback to the managed approaches (assuming they
* are specified, otherwise logging is simply disabled).
*/
static Connection proxy(JdbcConfiguration info, Connection connection, PrintWriter managedPrinter) {
return createProxy(Connection.class, new ConnectionProxy(logger(info, managedPrinter), connection));
}
static DatabaseMetaData proxy(DatabaseMetaDataProxy handler) {
return createProxy(DatabaseMetaData.class, handler);
}
static ParameterMetaData proxy(ParameterMetaDataProxy handler) {
return createProxy(ParameterMetaData.class, handler);
}
static ResultSet proxy(ResultSetProxy handler) {
return createProxy(ResultSet.class, handler);
}
static ResultSetMetaData proxy(ResultSetMetaDataProxy handler) {
return createProxy(ResultSetMetaData.class, handler);
}
static Statement proxy(Object statement, StatementProxy handler) {
Class extends Statement> i = Statement.class;
if (statement instanceof CallableStatement) {
i = CallableStatement.class;
} else if (statement instanceof PreparedStatement) {
i = PreparedStatement.class;
}
return createProxy(i, handler);
}
@SuppressWarnings("unchecked")
private static P createProxy(Class
proxy, InvocationHandler handler) {
return (P) Proxy.newProxyInstance(Debug.class.getClassLoader(), new Class>[] { DebugProxy.class, proxy }, handler);
}
private static DebugLog logger(JdbcConfiguration info, PrintWriter managedPrinter) {
DebugLog log = null;
if (managedPrinter != null) {
synchronized (Debug.class) {
log = OUTPUT_MANAGED.get(managedPrinter);
if (log == null) {
log = createLog(managedPrinter, info.flushAlways());
OUTPUT_MANAGED.put(managedPrinter, log);
}
}
}
String out = info.debugOut();
// System.out/err can be changed so do some checks
if ("err".equals(out)) {
PrintStream sys = stderr();
if (SYS_ERR == null) {
SYS_ERR = sys;
}
if (SYS_ERR != sys) {
SYS_ERR.flush();
SYS_ERR = sys;
ERR = null;
}
if (ERR == null) {
ERR = createLog(new PrintWriter(new OutputStreamWriter(sys, StandardCharsets.UTF_8)), info.flushAlways());
}
return ERR;
}
if ("out".equals(out)) {
PrintStream sys = stdout();
if (SYS_OUT == null) {
SYS_OUT = sys;
}
if (SYS_OUT != sys) {
SYS_OUT.flush();
SYS_OUT = sys;
OUT = null;
}
if (OUT == null) {
OUT = createLog(new PrintWriter(new OutputStreamWriter(sys, StandardCharsets.UTF_8)), info.flushAlways());
}
return OUT;
}
synchronized (Debug.class) {
log = OUTPUT_CACHE.get(out);
if (log == null) {
// must be local file
try {
PrintWriter print = new PrintWriter(Files.newBufferedWriter(Paths.get("").resolve(out), StandardCharsets.UTF_8));
log = createLog(print, info.flushAlways());
OUTPUT_CACHE.put(out, log);
OUTPUT_REFS.put(out, Integer.valueOf(0));
} catch (Exception ex) {
throw new JdbcException(ex, "Cannot open debug output [" + out + "]");
}
}
OUTPUT_REFS.put(out, Integer.valueOf(OUTPUT_REFS.get(out).intValue() + 1));
}
return log;
}
private static DebugLog createLog(PrintWriter print, boolean flushAlways) {
DebugLog log = new DebugLog(print, flushAlways);
log.logSystemInfo();
return log;
}
static void release(JdbcConfiguration info) {
if (info.debug() == false) {
return;
}
String out = info.debugOut();
synchronized (Debug.class) {
Integer ref = OUTPUT_REFS.get(out);
if (ref != null) {
int r = ref.intValue();
if (r < 2) {
OUTPUT_REFS.remove(out);
DebugLog d = OUTPUT_CACHE.remove(out);
if (d != null) {
if (d.print != null) {
d.print.close();
}
}
} else {
OUTPUT_REFS.put(out, Integer.valueOf(r - 1));
}
}
}
}
static synchronized void close() {
// clear the ref
OUTPUT_REFS.clear();
// clear the streams
for (DebugLog d : OUTPUT_CACHE.values()) {
if (d.print != null) {
d.print.close();
}
}
OUTPUT_CACHE.clear();
// flush the managed ones
for (DebugLog d : OUTPUT_MANAGED.values()) {
d.print.flush();
}
OUTPUT_MANAGED.clear();
}
@SuppressForbidden(reason = "JDBC drivers allows logging to Sys.out")
private static PrintStream stdout() {
return System.out;
}
@SuppressForbidden(reason = "JDBC drivers allows logging to Sys.err")
private static PrintStream stderr() {
return System.err;
}
}