package com.mongodb.internal.connection;
import com.mongodb.MongoCommandException;
import com.mongodb.connection.ByteBufferBsonOutput;
import com.mongodb.connection.ConnectionDescription;
import com.mongodb.diagnostics.logging.Logger;
import com.mongodb.event.CommandListener;
import org.bson.BsonDocument;
import org.bson.BsonInt32;
import org.bson.BsonReader;
import org.bson.codecs.RawBsonDocumentCodec;
import org.bson.json.JsonMode;
import org.bson.json.JsonWriter;
import org.bson.json.JsonWriterSettings;
import java.io.StringWriter;
import java.util.Set;
import static com.mongodb.internal.connection.ProtocolHelper.sendCommandFailedEvent;
import static com.mongodb.internal.connection.ProtocolHelper.sendCommandStartedEvent;
import static com.mongodb.internal.connection.ProtocolHelper.sendCommandSucceededEvent;
import static java.lang.String.format;
class LoggingCommandEventSender implements CommandEventSender {
private static final int MAX_COMMAND_DOCUMENT_LENGTH_TO_LOG = 1000;
private final Set<String> securitySensitiveCommands;
private final ConnectionDescription description;
private final CommandListener commandListener;
private final Logger logger;
private final long startTimeNanos;
private final CommandMessage message;
private final String commandName;
private volatile BsonDocument commandDocument;
LoggingCommandEventSender(final Set<String> securitySensitiveCommands, final ConnectionDescription description,
final CommandListener commandListener, final CommandMessage message,
final ByteBufferBsonOutput bsonOutput, final Logger logger) {
this.securitySensitiveCommands = securitySensitiveCommands;
this.description = description;
this.commandListener = commandListener;
this.logger = logger;
this.startTimeNanos = System.nanoTime();
this.message = message;
this.commandDocument = message.getCommandDocument(bsonOutput);
this.commandName = commandDocument.getFirstKey();
}
@Override
public void sendStartedEvent() {
if (loggingRequired()) {
logger.debug(
format("Sending command '%s' with request id %d to database %s on connection [%s] to server %s",
getTruncatedJsonCommand(), message.getId(),
message.getNamespace().getDatabaseName(), description.getConnectionId(), description.getServerAddress()));
}
if (eventRequired()) {
BsonDocument commandDocumentForEvent = (securitySensitiveCommands.contains(commandName))
? new BsonDocument() : commandDocument;
sendCommandStartedEvent(message, message.getNamespace().getDatabaseName(),
commandName, commandDocumentForEvent, description, commandListener);
}
commandDocument = null;
}
private String getTruncatedJsonCommand() {
StringWriter writer = new StringWriter();
BsonReader bsonReader = commandDocument.asBsonReader();
try {
JsonWriter jsonWriter = new JsonWriter(writer,
JsonWriterSettings.builder().outputMode(JsonMode.RELAXED).maxLength(MAX_COMMAND_DOCUMENT_LENGTH_TO_LOG).build());
jsonWriter.pipe(bsonReader);
if (jsonWriter.isTruncated()) {
writer.append(" ...");
}
return writer.toString();
} finally {
bsonReader.close();
}
}
@Override
public void sendFailedEvent(final Throwable t) {
Throwable commandEventException = t;
if (t instanceof MongoCommandException && (securitySensitiveCommands.contains(commandName))) {
commandEventException = new MongoCommandException(new BsonDocument(), description.getServerAddress());
}
long elapsedTimeNanos = System.nanoTime() - startTimeNanos;
if (loggingRequired()) {
logger.debug(
format("Execution of command with request id %d failed to complete successfully in %s ms on connection [%s] "
+ "to server %s",
message.getId(), getElapsedTimeFormattedInMilliseconds(elapsedTimeNanos), description.getConnectionId(),
description.getServerAddress()),
commandEventException);
}
if (eventRequired()) {
sendCommandFailedEvent(message, commandName, description, elapsedTimeNanos, commandEventException, commandListener);
}
}
@Override
public void sendSucceededEvent(final ResponseBuffers responseBuffers) {
long elapsedTimeNanos = System.nanoTime() - startTimeNanos;
if (loggingRequired()) {
logger.debug(
format("Execution of command with request id %d completed successfully in %s ms on connection [%s] to server %s",
message.getId(), getElapsedTimeFormattedInMilliseconds(elapsedTimeNanos), description.getConnectionId(),
description.getServerAddress()));
}
if (eventRequired()) {
BsonDocument responseDocumentForEvent = (securitySensitiveCommands.contains(commandName))
? new BsonDocument()
: responseBuffers.getResponseDocument(message.getId(), new RawBsonDocumentCodec());
sendCommandSucceededEvent(message, commandName, responseDocumentForEvent, description,
elapsedTimeNanos, commandListener);
}
}
@Override
public void sendSucceededEventForOneWayCommand() {
long elapsedTimeNanos = System.nanoTime() - startTimeNanos;
if (loggingRequired()) {
logger.debug(
format("Execution of one-way command with request id %d completed successfully in %s ms on connection [%s] "
+ "to server %s",
message.getId(), getElapsedTimeFormattedInMilliseconds(elapsedTimeNanos), description.getConnectionId(),
description.getServerAddress()));
}
if (eventRequired()) {
BsonDocument responseDocumentForEvent = new BsonDocument("ok", new BsonInt32(1));
sendCommandSucceededEvent(message, commandName, responseDocumentForEvent, description,
elapsedTimeNanos, commandListener);
}
}
private boolean loggingRequired() {
return logger.isDebugEnabled();
}
private boolean eventRequired() {
return commandListener != null;
}
private String getElapsedTimeFormattedInMilliseconds(final long elapsedTimeNanos) {
return DecimalFormatHelper.format("#0.00", elapsedTimeNanos / 1000000.0);
}
}