001/*
002 * Copyright 2015-2018 Transmogrify LLC.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.pyranid;
018
019import static java.lang.String.format;
020import static java.util.Objects.requireNonNull;
021import static java.util.stream.Collectors.joining;
022
023import java.util.ArrayList;
024import java.util.List;
025import java.util.logging.Level;
026import java.util.logging.Logger;
027
028/**
029 * Basic implementation of {@link StatementLogger} which logs to <code>{@value #LOGGER_NAME}</code> at
030 * {@link Level#FINE}.
031 * 
032 * @author <a href="http://revetkn.com">Mark Allen</a>
033 * @since 1.0.0
034 */
035public class DefaultStatementLogger implements StatementLogger {
036  /**
037   * The name of our logger.
038   */
039  public static final String LOGGER_NAME = "com.pyranid.SQL";
040
041  /**
042   * The level of our logger.
043   */
044  public static final Level LOGGER_LEVEL = Level.FINE;
045
046  /**
047   * The point at which we ellipsize output for parameters.
048   */
049  private static final int MAXIMUM_PARAMETER_LOGGING_LENGTH = 100;
050
051  private final Logger logger = Logger.getLogger(LOGGER_NAME);
052
053  @Override
054  public void log(StatementLog statementLog) {
055    requireNonNull(statementLog);
056
057    if (logger.isLoggable(LOGGER_LEVEL))
058      logger.log(LOGGER_LEVEL, formatStatementLog(statementLog));
059  }
060
061  protected String formatStatementLog(StatementLog statementLog) {
062    requireNonNull(statementLog);
063
064    List<String> timingEntries = new ArrayList<>(4);
065
066    if (statementLog.connectionAcquisitionTime().isPresent())
067      timingEntries.add(format("%.2fms acquiring connection",
068        statementLog.connectionAcquisitionTime().get() / 1_000_000f));
069
070    if (statementLog.preparationTime().isPresent())
071      timingEntries.add(format("%.2fms preparing statement", statementLog.preparationTime().get() / 1_000_000f));
072
073    if (statementLog.executionTime().isPresent())
074      timingEntries.add(format("%.2fms executing statement", statementLog.executionTime().get() / 1_000_000f));
075
076    if (statementLog.resultSetMappingTime().isPresent())
077      timingEntries.add(format("%.2fms processing resultset", statementLog.resultSetMappingTime().get() / 1_000_000f));
078
079    String parameterLine = null;
080
081    if (statementLog.parameters().size() > 0) {
082      StringBuilder parameterLineBuilder = new StringBuilder();
083      parameterLineBuilder.append("Parameters: ");
084      parameterLineBuilder.append(statementLog.parameters().stream().map(parameter -> {
085        if (parameter == null)
086          return "null";
087
088        if (parameter instanceof Number)
089          return format("%s", parameter);
090
091        if (parameter.getClass().isArray()) {
092          // TODO: cap size of arrays
093
094        if (parameter instanceof byte[])
095          return format("[byte array of length %d]", ((byte[]) parameter).length);
096      }
097
098      return format("'%s'", ellipsize(parameter.toString(), MAXIMUM_PARAMETER_LOGGING_LENGTH));
099    } ).collect(joining(", ")));
100
101      parameterLine = parameterLineBuilder.toString();
102    }
103
104    List<String> lines = new ArrayList<>(4);
105
106    lines.add(statementLog.sql());
107
108    if (parameterLine != null)
109      lines.add(parameterLine);
110
111    if (timingEntries.size() > 0)
112      lines.add(timingEntries.stream().collect(joining(", ")));
113
114    if (statementLog.exception().isPresent()) {
115      Throwable throwable = statementLog.exception().get();
116
117      if (throwable instanceof DatabaseException && throwable.getCause() != null)
118        throwable = throwable.getCause();
119
120      lines.add(format("Failed due to %s", throwable.toString()));
121    }
122
123    return lines.stream().collect(joining("\n"));
124  }
125
126  /**
127   * Ellipsizes the given {@code string}, capping at {@code maximumLength}.
128   * 
129   * @param string
130   *          the string to ellipsize
131   * @param maximumLength
132   *          the maximum length of the ellipsized string, not including ellipsis
133   * @return an ellipsized version of {@code string}
134   */
135  protected String ellipsize(String string, int maximumLength) {
136    requireNonNull(string);
137
138    string = string.trim();
139
140    if (string.length() <= maximumLength)
141      return string;
142
143    return format("%s...", string.substring(0, maximumLength));
144  }
145}