All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticsearch.xpack.sql.jdbc.Debug Maven / Gradle / Ivy

/*
 * 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 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; } }





© 2015 - 2026 Weber Informatics LLC | Privacy Policy