diff --git a/community/bolt/src/main/java/org/neo4j/bolt/BoltChannel.java b/community/bolt/src/main/java/org/neo4j/bolt/BoltChannel.java index 9eef5fe319733..31259641eb5db 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/BoltChannel.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/BoltChannel.java @@ -25,6 +25,8 @@ import java.net.SocketAddress; +import org.neo4j.bolt.logging.BoltMessageLogger; + /** * A channel through which Bolt messaging can occur. */ @@ -65,14 +67,19 @@ public BoltMessageLogger log() @Override public void close() { - messageLogger.serverEvent( "CLOSE" ); - try - { - rawChannel().close(); - } - catch ( Exception e ) + Channel rawChannel = rawChannel(); + if ( rawChannel.isOpen() ) { - // + messageLogger.serverEvent( "CLOSE" ); + try + { + // closing of channel is asynchronous, wait for it to complete + rawChannel.close().sync(); + } + catch ( InterruptedException e ) + { + Thread.currentThread().interrupt(); + } } } diff --git a/community/bolt/src/main/java/org/neo4j/bolt/BoltKernelExtension.java b/community/bolt/src/main/java/org/neo4j/bolt/BoltKernelExtension.java index 9e8a3dbab8cc0..5c9c419990433 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/BoltKernelExtension.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/BoltKernelExtension.java @@ -28,6 +28,7 @@ import java.util.function.Function; import java.util.stream.Collectors; +import org.neo4j.bolt.logging.BoltMessageLogging; import org.neo4j.bolt.security.auth.Authentication; import org.neo4j.bolt.security.auth.BasicAuthentication; import org.neo4j.bolt.transport.BoltMessagingProtocolHandler; @@ -48,6 +49,7 @@ import org.neo4j.graphdb.factory.GraphDatabaseSettings; import org.neo4j.helpers.ListenSocketAddress; import org.neo4j.helpers.Service; +import org.neo4j.io.fs.FileSystemAbstraction; import org.neo4j.kernel.api.bolt.BoltConnectionTracker; import org.neo4j.kernel.api.security.AuthManager; import org.neo4j.kernel.api.security.UserManagerSupplier; @@ -112,6 +114,8 @@ public interface Dependencies UserManagerSupplier userManagerSupplier(); SslPolicyLoader sslPolicyFactory(); + + FileSystemAbstraction fileSystem(); } public BoltKernelExtension() @@ -135,6 +139,7 @@ public Lifecycle newInstance( KernelContext context, Dependencies dependencies ) JobScheduler scheduler = dependencies.scheduler(); InternalLoggerFactory.setDefaultFactory( new Netty4LoggerFactory( logService.getInternalLogProvider() ) ); + BoltMessageLogging boltLogging = BoltMessageLogging.create( dependencies.fileSystem(), scheduler, config, log ); Authentication authentication = authentication( dependencies.authManager(), dependencies.userManagerSupplier() ); @@ -181,7 +186,7 @@ public Lifecycle newInstance( KernelContext context, Dependencies dependencies ) final Map> protocolHandlers = getProtocolHandlers( logService, workerFactory ); return new SocketTransport( listenAddress, sslCtx, requireEncryption, logService.getInternalLogProvider(), - BoltMessageLog.getInstance(), protocolHandlers ); + boltLogging, protocolHandlers ); } ) ); if ( connectors.size() > 0 && !config.get( GraphDatabaseSettings.disconnected ) ) diff --git a/community/bolt/src/main/java/org/neo4j/bolt/BoltMessageLog.java b/community/bolt/src/main/java/org/neo4j/bolt/BoltMessageLog.java deleted file mode 100644 index 06132bc0f8f0b..0000000000000 --- a/community/bolt/src/main/java/org/neo4j/bolt/BoltMessageLog.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (c) 2002-2017 "Neo Technology," - * Network Engine for Objects in Lund AB [http://neotechnology.com] - * - * This file is part of Neo4j. - * - * Neo4j is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.neo4j.bolt; - -import io.netty.channel.Channel; -import org.codehaus.jackson.map.ObjectMapper; -import org.neo4j.io.fs.DefaultFileSystemAbstraction; -import org.neo4j.io.fs.FileSystemAbstraction; -import org.neo4j.kernel.configuration.Config; -import org.neo4j.kernel.lifecycle.LifecycleAdapter; -import org.neo4j.logging.FormattedLog; -import org.neo4j.logging.Level; -import org.neo4j.logging.Log; -import org.neo4j.logging.RotatingFileOutputStreamSupplier; - -import java.io.File; -import java.io.IOException; -import java.net.SocketAddress; -import java.util.concurrent.Executor; - -public class BoltMessageLog extends LifecycleAdapter -{ - private RotatingFileOutputStreamSupplier rotatingSupplier; - private final Log inner; - - public static BoltMessageLog getInstance() - { - try - { - return new BoltMessageLog( null, new DefaultFileSystemAbstraction(), null ); - } - catch ( IOException e ) - { - return null; - } - } - - public BoltMessageLog( Config config, FileSystemAbstraction fileSystem, Executor executor ) - throws IOException - { - // TODO: draw settings from config, plumb in the executor, etc - FormattedLog.Builder builder = FormattedLog.withUTCTimeZone(); - File logFile = new File( "/tmp/bolt.log" ); - - rotatingSupplier = new RotatingFileOutputStreamSupplier( fileSystem, logFile, 256L, 1000L * 60L * 60L * 24L, 10, - null ); - - FormattedLog formattedLog = builder.toOutputStream( rotatingSupplier ); - formattedLog.setLevel( Level.DEBUG ); - - this.inner = formattedLog; - } - - public void error( Channel channel, String message ) - { - inner.error( "[%s] %s", remoteAddress( channel ), message ); - } - - public void error( Channel channel, String message, String arg1 ) - { - inner.error( "[%s] %s %s", remoteAddress( channel ), message, arg1 ); - } - - public void error( Channel channel, String message, String arg1, String arg2 ) - { - inner.error( "[%s] %s %s %s", remoteAddress( channel ), message, arg1, arg2 ); - } - - public void warn( Channel channel, String message ) - { - inner.warn( "[%s] %s", remoteAddress( channel ), message ); - } - - public void warn( Channel channel, String message, String arg1 ) - { - inner.warn( "[%s] %s %s", remoteAddress( channel ), message, arg1 ); - } - - public void warn( Channel channel, String message, String arg1, String arg2 ) - { - inner.warn( "[%s] %s %s %s", remoteAddress( channel ), message, arg1, arg2 ); - } - - public void info( Channel channel, String message ) - { - inner.info( "[%s] %s", remoteAddress( channel ), message ); - } - - public void info( Channel channel, String message, String arg1 ) - { - inner.info( "[%s] %s %s", remoteAddress( channel ), message, arg1 ); - } - - public void info( Channel channel, String message, String arg1, String arg2 ) - { - inner.info( "[%s] %s %s %s", remoteAddress( channel ), message, arg1, arg2 ); - } - - public void info( Channel channel, String message, String arg1, String arg2, String arg3 ) - { - inner.info( "[%s] %s %s %s", remoteAddress( channel ), message, arg1, arg2, arg3 ); - } - - public void debug( Channel channel, String message ) - { - inner.debug( "[%s] %s", remoteAddress( channel ), message ); - } - - public void debug( Channel channel, String message, String arg1 ) - { - inner.debug( "[%s] %s %s", remoteAddress( channel ), message, arg1 ); - } - - public void debug( Channel channel, String message, String arg1, String arg2 ) - { - inner.debug( "[%s] %s %s %s", remoteAddress( channel ), message, arg1, arg2 ); - } - - private SocketAddress remoteAddress( Channel channel ) - { - return channel == null ? null : channel.remoteAddress(); - } - -} diff --git a/community/bolt/src/main/java/org/neo4j/bolt/BoltMessageLogger.java b/community/bolt/src/main/java/org/neo4j/bolt/BoltMessageLogger.java deleted file mode 100644 index 6821e32bb4025..0000000000000 --- a/community/bolt/src/main/java/org/neo4j/bolt/BoltMessageLogger.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (c) 2002-2017 "Neo Technology," - * Network Engine for Objects in Lund AB [http://neotechnology.com] - * - * This file is part of Neo4j. - * - * Neo4j is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.neo4j.bolt; - -import io.netty.channel.Channel; -import org.codehaus.jackson.map.ObjectMapper; - -import java.io.IOException; -import java.util.Map; - -import static java.lang.String.format; - -/** - * Logs Bolt messages for a client-server pair. - */ -public class BoltMessageLogger -{ - private final BoltMessageLog messageLog; - private final Channel channel; - private final ObjectMapper jsonObjectMapper = new ObjectMapper(); - - public BoltMessageLogger( BoltMessageLog messageLog, Channel channel ) - { - this.messageLog = messageLog; - this.channel = channel; - } - - public void clientEvent( String eventName ) - { - messageLog.info( channel, format( "C: <%s>", eventName ) ); - } - - public void clientEvent( String eventName, Object arg1 ) - { - messageLog.info( channel, format( "C: <%s>", eventName ), json( arg1 ) ); - } - - public void clientError( String eventName, String arg1 ) - { - messageLog.error( channel, format( "C: <%s>", eventName ), json( arg1 ) ); - } - - public void clientError( String eventName, String arg1, String arg2 ) - { - messageLog.error( channel, format( "C: <%s>", eventName ), json( arg1 ), json( arg2 ) ); - } - - public void serverEvent( String eventName ) - { - messageLog.info( channel, format( "S: <%s>", eventName ) ); - } - - public void serverEvent( String eventName, Object arg1 ) - { - messageLog.info( channel, format( "S: <%s>", eventName ), json( arg1 ) ); - } - - public void serverError( String eventName, String arg1 ) - { - messageLog.error( channel, format( "S: <%s>", eventName ), json( arg1 ) ); - } - - public void serverError( String eventName, String arg1, String arg2 ) - { - messageLog.error( channel, format( "S: <%s>", eventName ), json( arg1 ), json( arg2 ) ); - } - - public void init( String userAgent, Map authToken ) - { - messageLog.info( channel, "C: INIT", json( userAgent ), "{...}" ); - } - - public void run( String statement, Map parameters ) - { - messageLog.info( channel, "C: RUN", json( statement ), json( parameters ) ); - } - - public void pullAll() - { - messageLog.info( channel, "C: PULL_ALL" ); - } - - public void discardAll() - { - messageLog.info( channel, "C: DISCARD_ALL" ); - } - - public void ackFailure() - { - messageLog.info( channel, "C: ACK_FAILURE" ); - } - - public void reset() - { - messageLog.info( channel, "C: RESET" ); - } - - public void success( Object metadata ) - { - messageLog.info( channel, "S: SUCCESS", json( metadata ) ); - } - - public void failure( String code, String message ) - { - messageLog.info( channel, "S: FAILURE", json( code ), json( message ) ); - } - - public void ignored() - { - messageLog.info( channel, "S: IGNORED" ); - } - - public void record( Object arg1 ) - { - messageLog.debug( channel, "S: RECORD", json( arg1 ) ); - } - - private String json( Object arg ) - { - try - { - return jsonObjectMapper.writeValueAsString( arg ); - } - catch ( IOException e ) - { - return "?"; - } - } - -} diff --git a/community/bolt/src/main/java/org/neo4j/bolt/logging/BoltMessageLog.java b/community/bolt/src/main/java/org/neo4j/bolt/logging/BoltMessageLog.java new file mode 100644 index 0000000000000..3b0e550d52474 --- /dev/null +++ b/community/bolt/src/main/java/org/neo4j/bolt/logging/BoltMessageLog.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.neo4j.bolt.logging; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; + +import org.neo4j.io.ByteUnit; +import org.neo4j.io.fs.FileSystemAbstraction; +import org.neo4j.kernel.lifecycle.LifecycleAdapter; +import org.neo4j.logging.FormattedLog; +import org.neo4j.logging.Level; +import org.neo4j.logging.Log; +import org.neo4j.logging.RotatingFileOutputStreamSupplier; + +public class BoltMessageLog extends LifecycleAdapter +{ + private static final long ROTATION_THRESHOLD_BYTES = ByteUnit.MebiByte.toBytes( 20 ); + private static final long ROTATION_DELAY_MS = TimeUnit.SECONDS.toMillis( 500 ); + private static final int MAX_ARCHIVES = 10; + + private final Log inner; + + public BoltMessageLog( FileSystemAbstraction fileSystem, File logFile, Executor executor ) throws IOException + { + RotatingFileOutputStreamSupplier outputStreamSupplier = new RotatingFileOutputStreamSupplier( fileSystem, + logFile, ROTATION_THRESHOLD_BYTES, ROTATION_DELAY_MS, MAX_ARCHIVES, executor ); + + FormattedLog formattedLog = FormattedLog.withUTCTimeZone().toOutputStream( outputStreamSupplier ); + formattedLog.setLevel( Level.DEBUG ); + + this.inner = formattedLog; + } + + public void error( String remoteAddress, String message ) + { + inner.error( "[%s] %s", remoteAddress, message ); + } + + public void error( String remoteAddress, String message, String arg1 ) + { + inner.error( "[%s] %s %s", remoteAddress, message, arg1 ); + } + + public void error( String remoteAddress, String message, String arg1, String arg2 ) + { + inner.error( "[%s] %s %s %s", remoteAddress, message, arg1, arg2 ); + } + + public void warn( String remoteAddress, String message ) + { + inner.warn( "[%s] %s", remoteAddress, message ); + } + + public void warn( String remoteAddress, String message, String arg1 ) + { + inner.warn( "[%s] %s %s", remoteAddress, message, arg1 ); + } + + public void warn( String remoteAddress, String message, String arg1, String arg2 ) + { + inner.warn( "[%s] %s %s %s", remoteAddress, message, arg1, arg2 ); + } + + public void info( String remoteAddress, String message ) + { + inner.info( "[%s] %s", remoteAddress, message ); + } + + public void info( String remoteAddress, String message, String arg1 ) + { + inner.info( "[%s] %s %s", remoteAddress, message, arg1 ); + } + + public void info( String remoteAddress, String message, String arg1, String arg2 ) + { + inner.info( "[%s] %s %s %s", remoteAddress, message, arg1, arg2 ); + } + + public void info( String remoteAddress, String message, String arg1, String arg2, String arg3 ) + { + inner.info( "[%s] %s %s %s", remoteAddress, message, arg1, arg2, arg3 ); + } + + public void debug( String remoteAddress, String message ) + { + inner.debug( "[%s] %s", remoteAddress, message ); + } + + public void debug( String remoteAddress, String message, String arg1 ) + { + inner.debug( "[%s] %s %s", remoteAddress, message, arg1 ); + } + + public void debug( String remoteAddress, String message, String arg1, String arg2 ) + { + inner.debug( "[%s] %s %s %s", remoteAddress, message, arg1, arg2 ); + } +} diff --git a/community/bolt/src/main/java/org/neo4j/bolt/logging/BoltMessageLogger.java b/community/bolt/src/main/java/org/neo4j/bolt/logging/BoltMessageLogger.java new file mode 100644 index 0000000000000..76050df0b1231 --- /dev/null +++ b/community/bolt/src/main/java/org/neo4j/bolt/logging/BoltMessageLogger.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.logging; + +import java.util.Map; +import java.util.function.Supplier; + +import org.neo4j.kernel.api.exceptions.Status; + +public interface BoltMessageLogger +{ + void clientEvent( String eventName ); + + void clientEvent( String eventName, Supplier detailsSupplier ); + + void clientError( String eventName, String message, Supplier detailsSupplier ); + + void serverEvent( String eventName ); + + void serverEvent( String eventName, Supplier detailsSupplier ); + + void serverError( String eventName, String message ); + + void serverError( String eventName, Status status, String message ); + + void init( String userAgent, Map authToken ); + + void run( String statement, Map parameters ); + + void pullAll(); + + void discardAll(); + + void ackFailure(); + + void reset(); + + void success( Object metadata ); + + void failure( Status status, String message ); + + void ignored(); + + void record( Object arg1 ); +} diff --git a/community/bolt/src/main/java/org/neo4j/bolt/logging/BoltMessageLoggerImpl.java b/community/bolt/src/main/java/org/neo4j/bolt/logging/BoltMessageLoggerImpl.java new file mode 100644 index 0000000000000..b1806933fbe10 --- /dev/null +++ b/community/bolt/src/main/java/org/neo4j/bolt/logging/BoltMessageLoggerImpl.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.neo4j.bolt.logging; + +import io.netty.channel.Channel; +import org.codehaus.jackson.map.ObjectMapper; + +import java.io.IOException; +import java.util.Map; +import java.util.function.Supplier; + +import org.neo4j.kernel.api.exceptions.Status; + +import static java.lang.String.format; + +/** + * Logs Bolt messages for a client-server pair. + */ +class BoltMessageLoggerImpl implements BoltMessageLogger +{ + private static final ObjectMapper jsonObjectMapper = new ObjectMapper(); + + private final BoltMessageLog messageLog; + private final String remoteAddress; + + BoltMessageLoggerImpl( BoltMessageLog messageLog, Channel channel ) + { + this.messageLog = messageLog; + this.remoteAddress = remoteAddress( channel ); + } + + @Override + public void clientEvent( String eventName ) + { + messageLog.info( remoteAddress, format( "C: <%s>", eventName ) ); + } + + @Override + public void clientEvent( String eventName, Supplier detailsSupplier ) + { + messageLog.info( remoteAddress, format( "C: <%s>", eventName ), detailsSupplier.get() ); + } + + @Override + public void clientError( String eventName, String message, Supplier detailsSupplier ) + { + messageLog.error( remoteAddress, format( "C: <%s>", eventName ), message, detailsSupplier.get() ); + } + + @Override + public void serverEvent( String eventName ) + { + messageLog.info( remoteAddress, format( "S: <%s>", eventName ) ); + } + + @Override + public void serverEvent( String eventName, Supplier detailsSupplier ) + { + messageLog.info( remoteAddress, format( "S: <%s>", eventName ), detailsSupplier.get() ); + } + + @Override + public void serverError( String eventName, String message ) + { + messageLog.error( remoteAddress, format( "S: <%s>", eventName ), message ); + } + + @Override + public void serverError( String eventName, Status status, String message ) + { + messageLog.error( remoteAddress, format( "S: <%s>", eventName ), status.code().serialize(), message ); + } + + @Override + public void init( String userAgent, Map authToken ) + { + // log only auth toke keys, not values that include password + messageLog.info( remoteAddress, "C: INIT", userAgent, json( authToken.keySet() ) ); + } + + @Override + public void run( String statement, Map parameters ) + { + messageLog.info( remoteAddress, "C: RUN", statement, json( parameters ) ); + } + + @Override + public void pullAll() + { + messageLog.info( remoteAddress, "C: PULL_ALL" ); + } + + @Override + public void discardAll() + { + messageLog.info( remoteAddress, "C: DISCARD_ALL" ); + } + + @Override + public void ackFailure() + { + messageLog.info( remoteAddress, "C: ACK_FAILURE" ); + } + + @Override + public void reset() + { + messageLog.info( remoteAddress, "C: RESET" ); + } + + @Override + public void success( Object metadata ) + { + messageLog.info( remoteAddress, "S: SUCCESS", json( metadata ) ); + } + + @Override + public void failure( Status status, String message ) + { + messageLog.info( remoteAddress, "S: FAILURE", status.code().serialize(), message ); + } + + @Override + public void ignored() + { + messageLog.info( remoteAddress, "S: IGNORED" ); + } + + @Override + public void record( Object arg1 ) + { + messageLog.debug( remoteAddress, "S: RECORD", json( arg1 ) ); + } + + private static String remoteAddress( Channel channel ) + { + return channel.remoteAddress().toString(); + } + + private static String json( Object arg ) + { + try + { + return jsonObjectMapper.writeValueAsString( arg ); + } + catch ( IOException e ) + { + return "?"; + } + } + +} diff --git a/community/bolt/src/main/java/org/neo4j/bolt/logging/BoltMessageLogging.java b/community/bolt/src/main/java/org/neo4j/bolt/logging/BoltMessageLogging.java new file mode 100644 index 0000000000000..8d67a267769c0 --- /dev/null +++ b/community/bolt/src/main/java/org/neo4j/bolt/logging/BoltMessageLogging.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.logging; + +import io.netty.channel.Channel; + +import java.io.File; +import java.util.concurrent.Executor; + +import org.neo4j.graphdb.factory.GraphDatabaseSettings; +import org.neo4j.io.fs.FileSystemAbstraction; +import org.neo4j.kernel.configuration.Config; +import org.neo4j.logging.Log; +import org.neo4j.scheduler.JobScheduler; + +public class BoltMessageLogging +{ + private final BoltMessageLog boltMessageLog; + + private BoltMessageLogging( BoltMessageLog boltMessageLog ) + { + this.boltMessageLog = boltMessageLog; + } + + public static BoltMessageLogging create( FileSystemAbstraction fs, JobScheduler scheduler, Config config, Log log ) + { + return new BoltMessageLogging( createBoltMessageLog( fs, scheduler, config, log ) ); + } + + public static BoltMessageLogging none() + { + return new BoltMessageLogging( null ); + } + + public BoltMessageLogger newLogger( Channel channel ) + { + return boltMessageLog == null ? NullBoltMessageLogger.getInstance() + : new BoltMessageLoggerImpl( boltMessageLog, channel ); + } + + private static BoltMessageLog createBoltMessageLog( FileSystemAbstraction fs, JobScheduler scheduler, + Config config, Log log ) + { + if ( config.get( GraphDatabaseSettings.bolt_logging_enabled ) ) + { + try + { + File boltLogFile = config.get( GraphDatabaseSettings.bolt_log_filename ); + Executor executor = scheduler.executor( JobScheduler.Groups.boltLogRotation ); + return new BoltMessageLog( fs, boltLogFile, executor ); + } + catch ( Throwable t ) + { + log.warn( "Unable to create bolt message log. It is thus disabled", t ); + } + } + return null; + } +} diff --git a/community/bolt/src/main/java/org/neo4j/bolt/logging/NullBoltMessageLogger.java b/community/bolt/src/main/java/org/neo4j/bolt/logging/NullBoltMessageLogger.java new file mode 100644 index 0000000000000..1d19dc346ff41 --- /dev/null +++ b/community/bolt/src/main/java/org/neo4j/bolt/logging/NullBoltMessageLogger.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.logging; + +import java.util.Map; +import java.util.function.Supplier; + +import org.neo4j.kernel.api.exceptions.Status; + +public class NullBoltMessageLogger implements BoltMessageLogger +{ + private static final NullBoltMessageLogger INSTANCE = new NullBoltMessageLogger(); + + private NullBoltMessageLogger() + { + } + + public static NullBoltMessageLogger getInstance() + { + return INSTANCE; + } + + @Override + public void clientEvent( String eventName ) + { + } + + @Override + public void clientEvent( String eventName, Supplier detailsSupplier ) + { + } + + @Override + public void clientError( String eventName, String message, Supplier detailsSupplier ) + { + } + + @Override + public void serverEvent( String eventName ) + { + } + + @Override + public void serverEvent( String eventName, Supplier detailsSupplier ) + { + } + + @Override + public void serverError( String eventName, String message ) + { + } + + @Override + public void serverError( String eventName, Status status, String message ) + { + } + + @Override + public void init( String userAgent, Map authToken ) + { + } + + @Override + public void run( String statement, Map parameters ) + { + } + + @Override + public void pullAll() + { + } + + @Override + public void discardAll() + { + } + + @Override + public void ackFailure() + { + } + + @Override + public void reset() + { + } + + @Override + public void success( Object metadata ) + { + } + + @Override + public void failure( Status status, String message ) + { + } + + @Override + public void ignored() + { + } + + @Override + public void record( Object arg1 ) + { + } +} diff --git a/community/bolt/src/main/java/org/neo4j/bolt/transport/BoltHandshakeProtocolHandler.java b/community/bolt/src/main/java/org/neo4j/bolt/transport/BoltHandshakeProtocolHandler.java index 2eeea08aebacb..f38fb1eaf9435 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/transport/BoltHandshakeProtocolHandler.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/transport/BoltHandshakeProtocolHandler.java @@ -20,13 +20,14 @@ package org.neo4j.bolt.transport; import io.netty.buffer.ByteBuf; -import org.neo4j.bolt.BoltChannel; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Map; import java.util.function.Function; +import org.neo4j.bolt.BoltChannel; + import static java.lang.String.format; /** @@ -86,20 +87,20 @@ else if ( handshakeBuffer.remaining() > buffer.readableBytes() ) // Verify that the handshake starts with a Bolt-shaped preamble. if ( handshakeBuffer.getInt() != BOLT_MAGIC_PREAMBLE ) { - boltChannel.log().clientError( "HANDSHAKE", format( "0x%08X", handshakeBuffer.getInt() ), - "Invalid Bolt signature" ); + boltChannel.log().clientError( "HANDSHAKE", "Invalid Bolt signature", + () -> format( "0x%08X", handshakeBuffer.getInt() ) ); return HandshakeOutcome.INVALID_HANDSHAKE; } else { - boltChannel.log().clientEvent( "HANDSHAKE", format( "0x%08X", BOLT_MAGIC_PREAMBLE ) ); + boltChannel.log().clientEvent( "HANDSHAKE", () -> format( "0x%08X", BOLT_MAGIC_PREAMBLE ) ); for ( int i = 0; i < 4; i++ ) { long suggestion = handshakeBuffer.getInt() & 0xFFFFFFFFL; if ( protocolHandlers.containsKey( suggestion ) ) { protocol = protocolHandlers.get( suggestion ).apply( boltChannel ); - boltChannel.log().serverEvent( "HANDSHAKE", format( "0x%02X", protocol.version() ) ); + boltChannel.log().serverEvent( "HANDSHAKE", () -> format( "0x%02X", protocol.version() ) ); return HandshakeOutcome.PROTOCOL_CHOSEN; } } diff --git a/community/bolt/src/main/java/org/neo4j/bolt/transport/SocketTransport.java b/community/bolt/src/main/java/org/neo4j/bolt/transport/SocketTransport.java index c283c9cec66c7..ad3eb28a8aaed 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/transport/SocketTransport.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/transport/SocketTransport.java @@ -24,14 +24,14 @@ import io.netty.channel.socket.SocketChannel; import io.netty.handler.ssl.SslContext; +import java.util.Map; +import java.util.function.Function; + import org.neo4j.bolt.BoltChannel; -import org.neo4j.bolt.BoltMessageLog; +import org.neo4j.bolt.logging.BoltMessageLogging; import org.neo4j.helpers.ListenSocketAddress; import org.neo4j.logging.LogProvider; -import java.util.Map; -import java.util.function.Function; - /** * Implements a transport for the Neo4j Messaging Protocol that uses good old regular sockets. */ @@ -41,18 +41,18 @@ public class SocketTransport implements NettyServer.ProtocolInitializer private final SslContext sslCtx; private final boolean encryptionRequired; private final LogProvider logging; - private final BoltMessageLog messageLog; + private final BoltMessageLogging boltLogging; private final Map> protocolVersions; public SocketTransport( ListenSocketAddress address, SslContext sslCtx, boolean encryptionRequired, - LogProvider logging, BoltMessageLog messageLog, + LogProvider logging, BoltMessageLogging boltLogging, Map> protocolVersions ) { this.address = address; this.sslCtx = sslCtx; this.encryptionRequired = encryptionRequired; this.logging = logging; - this.messageLog = messageLog; + this.boltLogging = boltLogging; this.protocolVersions = protocolVersions; } @@ -67,7 +67,7 @@ public void initChannel( SocketChannel ch ) throws Exception ch.config().setAllocator( PooledByteBufAllocator.DEFAULT ); ch.pipeline().addLast( new TransportSelectionHandler( sslCtx, encryptionRequired, false, logging, protocolVersions, - messageLog ) ); + boltLogging ) ); } }; } diff --git a/community/bolt/src/main/java/org/neo4j/bolt/transport/SocketTransportHandler.java b/community/bolt/src/main/java/org/neo4j/bolt/transport/SocketTransportHandler.java index 68de8434d0949..9300b8f07ea39 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/transport/SocketTransportHandler.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/transport/SocketTransportHandler.java @@ -24,8 +24,8 @@ import io.netty.channel.ChannelInboundHandlerAdapter; import org.neo4j.bolt.BoltChannel; -import org.neo4j.bolt.BoltMessageLog; -import org.neo4j.bolt.BoltMessageLogger; +import org.neo4j.bolt.logging.BoltMessageLogger; +import org.neo4j.bolt.logging.BoltMessageLogging; import org.neo4j.logging.Log; import org.neo4j.logging.LogProvider; @@ -41,16 +41,16 @@ public class SocketTransportHandler extends ChannelInboundHandlerAdapter { private final BoltHandshakeProtocolHandler handshake; private final Log log; - private final BoltMessageLog messageLog; + private final BoltMessageLogging boltLogging; private BoltMessagingProtocolHandler protocol; public SocketTransportHandler( BoltHandshakeProtocolHandler handshake, - LogProvider logging, BoltMessageLog messageLog ) + LogProvider logging, BoltMessageLogging boltLogging ) { this.handshake = handshake; this.log = logging.getLog( getClass() ); - this.messageLog = messageLog; + this.boltLogging = boltLogging; } @Override @@ -61,9 +61,9 @@ public void channelRead( ChannelHandlerContext ctx, Object msg ) throws Exceptio ByteBuf buffer = (ByteBuf) msg; if ( protocol == null ) { - BoltMessageLogger messageLogger = new BoltMessageLogger( messageLog, ctx.channel() ); - messageLogger.clientEvent( "OPEN" ); - performHandshake( BoltChannel.open( ctx, messageLogger ), buffer ); + BoltMessageLogger boltLogger = boltLogging.newLogger( ctx.channel() ); + boltLogger.clientEvent( "OPEN" ); + performHandshake( BoltChannel.open( ctx, boltLogger ), buffer ); } else { diff --git a/community/bolt/src/main/java/org/neo4j/bolt/transport/TransportSelectionHandler.java b/community/bolt/src/main/java/org/neo4j/bolt/transport/TransportSelectionHandler.java index 531caa8c43dd6..39a01e6ee4349 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/transport/TransportSelectionHandler.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/transport/TransportSelectionHandler.java @@ -34,7 +34,7 @@ import java.util.function.Function; import org.neo4j.bolt.BoltChannel; -import org.neo4j.bolt.BoltMessageLog; +import org.neo4j.bolt.logging.BoltMessageLogging; import org.neo4j.logging.LogProvider; import static org.neo4j.bolt.transport.BoltHandshakeProtocolHandler.BOLT_MAGIC_PREAMBLE; @@ -48,18 +48,18 @@ public class TransportSelectionHandler extends ByteToMessageDecoder private final boolean encryptionRequired; private final boolean isEncrypted; private final LogProvider logging; - private final BoltMessageLog messageLog; + private final BoltMessageLogging boltLogging; private final Map> protocolVersions; TransportSelectionHandler( SslContext sslCtx, boolean encryptionRequired, boolean isEncrypted, LogProvider logging, Map> protocolVersions, - BoltMessageLog messageLog ) + BoltMessageLogging boltLogging ) { this.sslCtx = sslCtx; this.encryptionRequired = encryptionRequired; this.isEncrypted = isEncrypted; this.logging = logging; - this.messageLog = messageLog; + this.boltLogging = boltLogging; this.protocolVersions = protocolVersions; } @@ -118,16 +118,15 @@ private void enableSsl( ChannelHandlerContext ctx ) { ChannelPipeline p = ctx.pipeline(); p.addLast( sslCtx.newHandler( ctx.alloc() ) ); - p.addLast( new TransportSelectionHandler( null, encryptionRequired, true, logging, protocolVersions, - messageLog ) ); + p.addLast( new TransportSelectionHandler( null, encryptionRequired, true, logging, + protocolVersions, boltLogging ) ); p.remove( this ); } private void switchToSocket( ChannelHandlerContext ctx ) { ChannelPipeline p = ctx.pipeline(); - p.addLast( new SocketTransportHandler( - new BoltHandshakeProtocolHandler( protocolVersions, encryptionRequired, isEncrypted ), logging, messageLog ) ); + p.addLast( newSocketTransportHandler() ); p.remove( this ); } @@ -139,8 +138,14 @@ private void switchToWebsocket( ChannelHandlerContext ctx ) new HttpObjectAggregator( MAX_WEBSOCKET_HANDSHAKE_SIZE ), new WebSocketServerProtocolHandler( "/" ), new WebSocketFrameTranslator(), - new SocketTransportHandler( - new BoltHandshakeProtocolHandler( protocolVersions, encryptionRequired, isEncrypted ), logging, messageLog ) ); + newSocketTransportHandler() ); p.remove( this ); } + + private SocketTransportHandler newSocketTransportHandler() + { + BoltHandshakeProtocolHandler protocolHandler = new BoltHandshakeProtocolHandler( protocolVersions, + encryptionRequired, isEncrypted ); + return new SocketTransportHandler( protocolHandler, logging, boltLogging ); + } } diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v1/messaging/BoltMessageRouter.java b/community/bolt/src/main/java/org/neo4j/bolt/v1/messaging/BoltMessageRouter.java index 17c35465847ce..d8656b92a3553 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/v1/messaging/BoltMessageRouter.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/v1/messaging/BoltMessageRouter.java @@ -22,7 +22,7 @@ import java.io.IOException; import java.util.Map; -import org.neo4j.bolt.BoltMessageLogger; +import org.neo4j.bolt.logging.BoltMessageLogger; import org.neo4j.bolt.v1.runtime.BoltWorker; import org.neo4j.bolt.v1.runtime.Neo4jError; import org.neo4j.bolt.v1.runtime.spi.BoltResult; @@ -95,7 +95,7 @@ public void onRun( String statement, Map params ) @Override public void onExternalError( Neo4jError error ) { - messageLogger.clientEvent("ERROR", error.message()); + messageLogger.clientEvent( "ERROR", error::message ); worker.enqueue( session -> session.externalError( error, defaultHandler ) ); } diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v1/messaging/BoltResponseMessageWriter.java b/community/bolt/src/main/java/org/neo4j/bolt/v1/messaging/BoltResponseMessageWriter.java index ad0420efaf2b2..979b0ef50f47f 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/v1/messaging/BoltResponseMessageWriter.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/v1/messaging/BoltResponseMessageWriter.java @@ -21,7 +21,7 @@ import java.io.IOException; -import org.neo4j.bolt.BoltMessageLogger; +import org.neo4j.bolt.logging.BoltMessageLogger; import org.neo4j.cypher.result.QueryResult; import org.neo4j.kernel.api.exceptions.Status; import org.neo4j.values.AnyValue; @@ -97,7 +97,7 @@ public void onIgnored() throws IOException @Override public void onFailure( Status status, String message ) throws IOException { - messageLogger.failure( status.code().serialize(), message ); + messageLogger.failure( status, message ); packer.packStructHeader( 1, FAILURE.signature() ); packer.packMapHeader( 2 ); @@ -113,7 +113,7 @@ public void onFailure( Status status, String message ) throws IOException @Override public void onFatal( Status status, String message ) throws IOException { - messageLogger.serverError( "FATAL", status.code().serialize(), message ); + messageLogger.serverError( "FATAL", status, message ); onFailure( status, message ); flush(); } diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v1/runtime/BoltFactoryImpl.java b/community/bolt/src/main/java/org/neo4j/bolt/v1/runtime/BoltFactoryImpl.java index 1853650cb8952..4250e2d7f840e 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/v1/runtime/BoltFactoryImpl.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/v1/runtime/BoltFactoryImpl.java @@ -87,7 +87,7 @@ public BoltStateMachine newMachine( BoltChannel boltChannel, Clock clock ) TransactionStateMachine.SPI transactionSPI = createTxSpi( clock ); BoltStateMachine.SPI boltSPI = new BoltStateMachineSPI( boltChannel, usageData, logging, authentication, connectionTracker, transactionSPI ); - return new BoltStateMachine( boltSPI, boltChannel::close, clock ); + return new BoltStateMachine( boltSPI, boltChannel, clock ); } private TransactionStateMachine.SPI createTxSpi( Clock clock ) diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v1/runtime/BoltStateMachine.java b/community/bolt/src/main/java/org/neo4j/bolt/v1/runtime/BoltStateMachine.java index 8010200d726ff..deef74a9dceed 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/v1/runtime/BoltStateMachine.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/v1/runtime/BoltStateMachine.java @@ -25,6 +25,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import org.neo4j.bolt.BoltChannel; import org.neo4j.bolt.BoltConnectionDescriptor; import org.neo4j.bolt.security.auth.AuthenticationException; import org.neo4j.bolt.security.auth.AuthenticationResult; @@ -59,7 +60,7 @@ public class BoltStateMachine implements AutoCloseable, ManagedBoltStateMachine { private final String id = UUID.randomUUID().toString(); - private final Runnable onClose; + private final BoltChannel boltChannel; private final Clock clock; State state = State.CONNECTED; @@ -67,11 +68,11 @@ public class BoltStateMachine implements AutoCloseable, ManagedBoltStateMachine final SPI spi; final MutableConnectionState ctx; - public BoltStateMachine( SPI spi, Runnable onClose, Clock clock ) + public BoltStateMachine( SPI spi, BoltChannel boltChannel, Clock clock ) { this.spi = spi; this.ctx = new MutableConnectionState( spi, clock ); - this.onClose = onClose; + this.boltChannel = boltChannel; this.clock = clock; } @@ -288,11 +289,7 @@ public void close() { try { - //Only run onClose, once - if ( !ctx.closed && onClose != null ) - { - onClose.run(); - } + boltChannel.close(); } finally { diff --git a/community/bolt/src/test/java/org/neo4j/bolt/BoltChannelTest.java b/community/bolt/src/test/java/org/neo4j/bolt/BoltChannelTest.java new file mode 100644 index 0000000000000..5570ad46f6718 --- /dev/null +++ b/community/bolt/src/test/java/org/neo4j/bolt/BoltChannelTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import org.neo4j.bolt.logging.BoltMessageLogger; + +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith( MockitoJUnitRunner.class ) +public class BoltChannelTest +{ + @Mock + private ChannelHandlerContext channelHandlerContext; + @Mock + private BoltMessageLogger messageLogger; + + @Test + public void shouldLogWhenOpened() + { + BoltChannel boltChannel = BoltChannel.open( channelHandlerContext, messageLogger ); + assertNotNull( boltChannel ); + + verify( messageLogger ).serverEvent( "OPEN" ); + } + + @Test + public void shouldLogWhenClosed() + { + Channel channel = channelMock( true ); + when( channelHandlerContext.channel() ).thenReturn( channel ); + BoltChannel boltChannel = BoltChannel.open( channelHandlerContext, messageLogger ); + assertNotNull( boltChannel ); + + boltChannel.close(); + + InOrder inOrder = inOrder( messageLogger ); + inOrder.verify( messageLogger ).serverEvent( "OPEN" ); + inOrder.verify( messageLogger ).serverEvent( "CLOSE" ); + } + + @Test + public void shouldCloseUnderlyingChannelWhenItIsOpen() + { + Channel channel = channelMock( true ); + when( channelHandlerContext.channel() ).thenReturn( channel ); + BoltChannel boltChannel = BoltChannel.open( channelHandlerContext, messageLogger ); + + boltChannel.close(); + + verify( channel ).close(); + } + + @Test + public void shouldNotCloseUnderlyingChannelWhenItIsClosed() + { + Channel channel = channelMock( false ); + when( channelHandlerContext.channel() ).thenReturn( channel ); + BoltChannel boltChannel = BoltChannel.open( channelHandlerContext, messageLogger ); + + boltChannel.close(); + + verify( channel, never() ).close(); + } + + private static Channel channelMock( boolean open ) + { + Channel channel = mock( Channel.class ); + when( channel.isOpen() ).thenReturn( open ); + when( channel.close() ).thenReturn( mock( ChannelFuture.class ) ); + return channel; + } +} diff --git a/community/bolt/src/test/java/org/neo4j/bolt/logging/BoltMessageLoggingTest.java b/community/bolt/src/test/java/org/neo4j/bolt/logging/BoltMessageLoggingTest.java new file mode 100644 index 0000000000000..855a92125ff3b --- /dev/null +++ b/community/bolt/src/test/java/org/neo4j/bolt/logging/BoltMessageLoggingTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.logging; + +import io.netty.channel.Channel; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.io.IOException; + +import org.neo4j.io.fs.FileSystemAbstraction; +import org.neo4j.kernel.configuration.Config; +import org.neo4j.logging.Log; +import org.neo4j.scheduler.JobScheduler; + +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.startsWith; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.neo4j.graphdb.factory.GraphDatabaseSettings.bolt_logging_enabled; +import static org.neo4j.helpers.collection.MapUtil.stringMap; + +@RunWith( MockitoJUnitRunner.class ) +public class BoltMessageLoggingTest +{ + @Mock( answer = Answers.RETURNS_MOCKS ) + private FileSystemAbstraction fs; + @Mock( answer = Answers.RETURNS_MOCKS ) + private JobScheduler jobScheduler; + @Mock( answer = Answers.RETURNS_MOCKS ) + private Log log; + @Mock( answer = Answers.RETURNS_MOCKS ) + private Channel channel; + + @Test + public void shouldCreateNullLoggerWhenDisabled() + { + Config config = newConfig( false ); + + BoltMessageLogging logging = BoltMessageLogging.create( fs, jobScheduler, config, log ); + BoltMessageLogger logger = logging.newLogger( channel ); + + assertThat( logger, instanceOf( NullBoltMessageLogger.class ) ); + } + + @Test + public void shouldCreateNullLoggerWhenUnableToCreateRealLogger() throws IOException + { + Config config = newConfig( true ); + IOException fsError = new IOException(); + when( fs.openAsOutputStream( any(), anyBoolean() ) ).thenThrow( fsError ); + + BoltMessageLogging logging = BoltMessageLogging.create( fs, jobScheduler, config, log ); + BoltMessageLogger logger = logging.newLogger( channel ); + + assertThat( logger, instanceOf( NullBoltMessageLogger.class ) ); + verify( log ).warn( startsWith( "Unable to create bolt message log" ), eq( fsError ) ); + } + + @Test + public void shouldCreateRealLoggerWhenEnabled() + { + Config config = newConfig( true ); + + BoltMessageLogging logging = BoltMessageLogging.create( fs, jobScheduler, config, log ); + BoltMessageLogger logger = logging.newLogger( channel ); + + assertThat( logger, instanceOf( BoltMessageLoggerImpl.class ) ); + } + + @Test + public void shouldCreateNullLoggerWhenNone() + { + BoltMessageLogging logging = BoltMessageLogging.none(); + BoltMessageLogger logger = logging.newLogger( channel ); + + assertThat( logger, instanceOf( NullBoltMessageLogger.class ) ); + } + + private static Config newConfig( boolean boltLogEnabled ) + { + return Config.defaults( stringMap( bolt_logging_enabled.name(), Boolean.toString( boltLogEnabled ) ) ); + } +} diff --git a/community/bolt/src/test/java/org/neo4j/bolt/v1/messaging/BoltResponseMessageTest.java b/community/bolt/src/test/java/org/neo4j/bolt/v1/messaging/BoltResponseMessageTest.java index d2794982b55cd..a4982c9bf4e7b 100644 --- a/community/bolt/src/test/java/org/neo4j/bolt/v1/messaging/BoltResponseMessageTest.java +++ b/community/bolt/src/test/java/org/neo4j/bolt/v1/messaging/BoltResponseMessageTest.java @@ -26,7 +26,7 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; -import org.neo4j.bolt.BoltMessageLogger; +import org.neo4j.bolt.logging.NullBoltMessageLogger; import org.neo4j.bolt.v1.messaging.message.FailureMessage; import org.neo4j.bolt.v1.messaging.message.IgnoredMessage; import org.neo4j.bolt.v1.messaging.message.RecordMessage; @@ -47,7 +47,6 @@ import static java.util.Collections.emptyList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; -import static org.mockito.Mockito.mock; import static org.neo4j.bolt.v1.messaging.BoltResponseMessageWriter.NO_BOUNDARY_HOOK; import static org.neo4j.bolt.v1.messaging.example.Paths.PATH_WITH_LENGTH_ONE; import static org.neo4j.bolt.v1.messaging.example.Paths.PATH_WITH_LENGTH_TWO; @@ -232,7 +231,7 @@ private T serializeAndDeserialize( T msg ) throws IO new Neo4jPack.Unpacker( new BufferedChannelInput( 16 ).reset( channel ) ) ); BoltResponseMessageWriter writer = new BoltResponseMessageWriter( new Neo4jPack.Packer( new BufferedChannelOutput( channel ) ), NO_BOUNDARY_HOOK, - mock( BoltMessageLogger.class ) ); + NullBoltMessageLogger.getInstance() ); msg.dispatch( writer ); writer.flush(); diff --git a/community/bolt/src/test/java/org/neo4j/bolt/v1/messaging/util/MessageMatchers.java b/community/bolt/src/test/java/org/neo4j/bolt/v1/messaging/util/MessageMatchers.java index 7cb3ad730d106..be315ed24fc1c 100644 --- a/community/bolt/src/test/java/org/neo4j/bolt/v1/messaging/util/MessageMatchers.java +++ b/community/bolt/src/test/java/org/neo4j/bolt/v1/messaging/util/MessageMatchers.java @@ -32,7 +32,7 @@ import java.util.Set; import java.util.stream.Collectors; -import org.neo4j.bolt.BoltMessageLogger; +import org.neo4j.bolt.logging.NullBoltMessageLogger; import org.neo4j.bolt.v1.messaging.BoltRequestMessageReader; import org.neo4j.bolt.v1.messaging.BoltRequestMessageRecorder; import org.neo4j.bolt.v1.messaging.BoltRequestMessageWriter; @@ -66,7 +66,6 @@ import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.mock; import static org.neo4j.bolt.v1.messaging.BoltResponseMessageWriter.NO_BOUNDARY_HOOK; public class MessageMatchers @@ -317,7 +316,7 @@ public static byte[] serialize( ResponseMessage... messages ) throws IOException { final RecordingByteChannel rawData = new RecordingByteChannel(); final BoltResponseMessageWriter packer = new BoltResponseMessageWriter( new Neo4jPack.Packer( new - BufferedChannelOutput( rawData ) ), NO_BOUNDARY_HOOK, mock( BoltMessageLogger.class ) ); + BufferedChannelOutput( rawData ) ), NO_BOUNDARY_HOOK, NullBoltMessageLogger.getInstance() ); for ( ResponseMessage message : messages ) { diff --git a/community/bolt/src/test/java/org/neo4j/bolt/v1/runtime/BoltStateMachineTest.java b/community/bolt/src/test/java/org/neo4j/bolt/v1/runtime/BoltStateMachineTest.java index 10c9067327c0b..6c69ebfa2bdfd 100644 --- a/community/bolt/src/test/java/org/neo4j/bolt/v1/runtime/BoltStateMachineTest.java +++ b/community/bolt/src/test/java/org/neo4j/bolt/v1/runtime/BoltStateMachineTest.java @@ -24,6 +24,7 @@ import java.time.Clock; import java.util.Collections; +import org.neo4j.bolt.BoltChannel; import org.neo4j.bolt.testing.BoltResponseRecorder; import org.neo4j.bolt.v1.runtime.spi.BoltResult; import org.neo4j.graphdb.TransactionFailureException; @@ -481,7 +482,8 @@ public void shouldCallOnTerminateWhenClosing() throws Throwable { // Given BoltStateMachineSPI spi = mock( BoltStateMachineSPI.class, RETURNS_MOCKS ); - final BoltStateMachine machine = new BoltStateMachine( spi, null, Clock.systemUTC() ); + BoltChannel boltChannel = mock( BoltChannel.class ); + final BoltStateMachine machine = new BoltStateMachine( spi, boltChannel, Clock.systemUTC() ); // When machine.close(); @@ -489,4 +491,16 @@ public void shouldCallOnTerminateWhenClosing() throws Throwable // Then verify( spi ).onTerminate( machine ); } + + @Test + public void shouldCloseBoltChannelWhenClosed() + { + BoltStateMachineSPI spi = mock( BoltStateMachineSPI.class ); + BoltChannel boltChannel = mock( BoltChannel.class ); + BoltStateMachine machine = new BoltStateMachine( spi, boltChannel, Clock.systemUTC() ); + + machine.close(); + + verify( boltChannel ).close(); + } } diff --git a/community/bolt/src/test/java/org/neo4j/bolt/v1/runtime/MachineRoom.java b/community/bolt/src/test/java/org/neo4j/bolt/v1/runtime/MachineRoom.java index d9bfdee05b8d6..89740d95c2231 100644 --- a/community/bolt/src/test/java/org/neo4j/bolt/v1/runtime/MachineRoom.java +++ b/community/bolt/src/test/java/org/neo4j/bolt/v1/runtime/MachineRoom.java @@ -22,6 +22,7 @@ import java.time.Clock; import java.util.Map; +import org.neo4j.bolt.BoltChannel; import org.neo4j.bolt.security.auth.AuthenticationException; import org.neo4j.bolt.security.auth.AuthenticationResult; @@ -48,7 +49,8 @@ private MachineRoom() public static BoltStateMachine newMachine() { - return new BoltStateMachine( mock( BoltStateMachineSPI.class, RETURNS_MOCKS ), null, Clock.systemUTC() ); + BoltChannel boltChannel = mock( BoltChannel.class ); + return new BoltStateMachine( mock( BoltStateMachineSPI.class, RETURNS_MOCKS ), boltChannel, Clock.systemUTC() ); } public static BoltStateMachine newMachine( BoltStateMachine.State state ) throws AuthenticationException, BoltConnectionFatality @@ -75,7 +77,8 @@ public static BoltStateMachine newMachineWithTransactionSPI( TransactionStateMac BoltStateMachine.SPI spi = mock( BoltStateMachine.SPI.class, RETURNS_MOCKS ); when( spi.transactionSpi() ).thenReturn( transactionSPI ); - BoltStateMachine machine = new BoltStateMachine( spi, null, Clock.systemUTC() ); + BoltChannel boltChannel = mock( BoltChannel.class ); + BoltStateMachine machine = new BoltStateMachine( spi, boltChannel, Clock.systemUTC() ); init( machine ); return machine; } diff --git a/community/bolt/src/test/java/org/neo4j/bolt/v1/runtime/ResetFuzzTest.java b/community/bolt/src/test/java/org/neo4j/bolt/v1/runtime/ResetFuzzTest.java index c6455c0257020..9fb8832bb466f 100644 --- a/community/bolt/src/test/java/org/neo4j/bolt/v1/runtime/ResetFuzzTest.java +++ b/community/bolt/src/test/java/org/neo4j/bolt/v1/runtime/ResetFuzzTest.java @@ -32,8 +32,7 @@ import org.neo4j.bolt.BoltChannel; import org.neo4j.bolt.BoltConnectionDescriptor; -import org.neo4j.bolt.BoltMessageLog; -import org.neo4j.bolt.BoltMessageLogger; +import org.neo4j.bolt.logging.NullBoltMessageLogger; import org.neo4j.bolt.security.auth.AuthenticationException; import org.neo4j.bolt.security.auth.AuthenticationResult; import org.neo4j.bolt.testing.BoltResponseRecorder; @@ -80,7 +79,7 @@ public class ResetFuzzTest private final AtomicLong liveTransactions = new AtomicLong(); private final Neo4jJobScheduler scheduler = life.add(new Neo4jJobScheduler()); private final Clock clock = Clock.systemUTC(); - private final BoltStateMachine machine = new BoltStateMachine( new FuzzStubSPI(), null, clock ); + private final BoltStateMachine machine = new BoltStateMachine( new FuzzStubSPI(), mock( BoltChannel.class ), clock ); private final ThreadedWorkerFactory workerFactory = new ThreadedWorkerFactory( ( boltChannel, clock ) -> machine, scheduler, NullLogService.getInstance(), clock ); private final BoltChannel boltChannel = mock( BoltChannel.class ); @@ -101,9 +100,9 @@ public void shouldAlwaysReturnToReadyAfterReset() throws Throwable BoltWorker boltWorker = workerFactory.newWorker( boltChannel ); boltWorker.enqueue( session -> session.init( "ResetFuzzTest/0.0", map(), nullResponseHandler() ) ); - BoltMessageLogger messageLogger = new BoltMessageLogger( BoltMessageLog.getInstance(), null ); + NullBoltMessageLogger boltLogger = NullBoltMessageLogger.getInstance(); BoltMessageRouter router = new BoltMessageRouter( - NullLog.getInstance(), messageLogger, boltWorker, new BoltResponseMessageHandler() + NullLog.getInstance(), boltLogger, boltWorker, new BoltResponseMessageHandler() { @Override public void onRecord( QueryResult.Record item ) throws IOException diff --git a/community/bolt/src/test/java/org/neo4j/bolt/v1/transport/BoltHandshakeProtocolHandlerTest.java b/community/bolt/src/test/java/org/neo4j/bolt/v1/transport/BoltHandshakeProtocolHandlerTest.java index 54cd0b5c1c788..bd221e1c299d6 100644 --- a/community/bolt/src/test/java/org/neo4j/bolt/v1/transport/BoltHandshakeProtocolHandlerTest.java +++ b/community/bolt/src/test/java/org/neo4j/bolt/v1/transport/BoltHandshakeProtocolHandlerTest.java @@ -29,11 +29,11 @@ import java.util.function.Function; import org.neo4j.bolt.BoltChannel; -import org.neo4j.bolt.BoltMessageLog; -import org.neo4j.bolt.BoltMessageLogger; +import org.neo4j.bolt.logging.BoltMessageLogger; +import org.neo4j.bolt.logging.NullBoltMessageLogger; +import org.neo4j.bolt.transport.BoltHandshakeProtocolHandler; import org.neo4j.bolt.transport.BoltMessagingProtocolHandler; import org.neo4j.bolt.transport.HandshakeOutcome; -import org.neo4j.bolt.transport.BoltHandshakeProtocolHandler; import static io.netty.buffer.Unpooled.wrappedBuffer; import static org.hamcrest.CoreMatchers.equalTo; @@ -53,8 +53,7 @@ public class BoltHandshakeProtocolHandlerTest private final Function factory = mock( Function.class ); private final BoltMessagingProtocolHandler protocol = mock( BoltMessagingProtocolHandler.class ); private final ChannelHandlerContext ctx = mock( ChannelHandlerContext.class ); - private final BoltMessageLog messageLog = BoltMessageLog.getInstance(); - private final BoltMessageLogger messageLogger = new BoltMessageLogger( messageLog, ctx.channel() ); + private final BoltMessageLogger messageLogger = NullBoltMessageLogger.getInstance(); @Test public void shouldChooseFirstAvailableProtocol() throws Throwable diff --git a/community/bolt/src/test/java/org/neo4j/bolt/v1/transport/BoltMessagingProtocolV1HandlerTest.java b/community/bolt/src/test/java/org/neo4j/bolt/v1/transport/BoltMessagingProtocolV1HandlerTest.java index 6eb5f443b8869..a99901ca7d8d5 100644 --- a/community/bolt/src/test/java/org/neo4j/bolt/v1/transport/BoltMessagingProtocolV1HandlerTest.java +++ b/community/bolt/src/test/java/org/neo4j/bolt/v1/transport/BoltMessagingProtocolV1HandlerTest.java @@ -30,7 +30,6 @@ import java.util.Objects; import org.neo4j.bolt.BoltChannel; -import org.neo4j.bolt.BoltMessageLogger; import org.neo4j.bolt.v1.runtime.BoltStateMachine; import org.neo4j.bolt.v1.runtime.BoltWorker; import org.neo4j.bolt.v1.runtime.SynchronousBoltWorker; diff --git a/community/bolt/src/test/java/org/neo4j/bolt/v1/transport/socket/FragmentedMessageDeliveryTest.java b/community/bolt/src/test/java/org/neo4j/bolt/v1/transport/socket/FragmentedMessageDeliveryTest.java index 520a67c1d8f18..0c0f0f6cb8f05 100644 --- a/community/bolt/src/test/java/org/neo4j/bolt/v1/transport/socket/FragmentedMessageDeliveryTest.java +++ b/community/bolt/src/test/java/org/neo4j/bolt/v1/transport/socket/FragmentedMessageDeliveryTest.java @@ -29,8 +29,7 @@ import java.util.Arrays; import org.neo4j.bolt.BoltChannel; -import org.neo4j.bolt.BoltMessageLog; -import org.neo4j.bolt.BoltMessageLogger; +import org.neo4j.bolt.logging.NullBoltMessageLogger; import org.neo4j.bolt.v1.messaging.BoltRequestMessageWriter; import org.neo4j.bolt.v1.messaging.Neo4jPack; import org.neo4j.bolt.v1.messaging.RecordingByteChannel; @@ -122,7 +121,7 @@ private void testPermutation( byte[] unfragmented, ByteBuf[] fragments ) throws BoltChannel boltChannel = mock( BoltChannel.class ); when( boltChannel.channelHandlerContext() ).thenReturn( ctx ); when( boltChannel.rawChannel() ).thenReturn( ch ); - when( boltChannel.log() ).thenReturn( mock( BoltMessageLogger.class ) ); + when( boltChannel.log() ).thenReturn( NullBoltMessageLogger.getInstance() ); BoltMessagingProtocolV1Handler protocol = new BoltMessagingProtocolV1Handler( boltChannel, new SynchronousBoltWorker( machine ), NullLogService.getInstance() ); diff --git a/community/bolt/src/test/java/org/neo4j/bolt/v1/transport/socket/SocketTransportHandlerTest.java b/community/bolt/src/test/java/org/neo4j/bolt/v1/transport/socket/SocketTransportHandlerTest.java index 9131b65861fcb..377b793fd1202 100644 --- a/community/bolt/src/test/java/org/neo4j/bolt/v1/transport/socket/SocketTransportHandlerTest.java +++ b/community/bolt/src/test/java/org/neo4j/bolt/v1/transport/socket/SocketTransportHandlerTest.java @@ -30,16 +30,16 @@ import java.util.function.Function; import org.neo4j.bolt.BoltChannel; -import org.neo4j.bolt.BoltMessageLog; -import org.neo4j.bolt.BoltMessageLogger; -import org.neo4j.bolt.transport.BoltMessagingProtocolHandler; +import org.neo4j.bolt.logging.BoltMessageLogging; import org.neo4j.bolt.transport.BoltHandshakeProtocolHandler; +import org.neo4j.bolt.transport.BoltMessagingProtocolHandler; import org.neo4j.bolt.transport.SocketTransportHandler; import org.neo4j.bolt.v1.runtime.BoltStateMachine; import org.neo4j.bolt.v1.runtime.SynchronousBoltWorker; import org.neo4j.bolt.v1.transport.BoltMessagingProtocolV1Handler; import org.neo4j.kernel.impl.logging.NullLogService; import org.neo4j.logging.AssertableLogProvider; +import org.neo4j.logging.LogProvider; import org.neo4j.logging.NullLogProvider; import static org.hamcrest.Matchers.equalTo; @@ -52,6 +52,9 @@ public class SocketTransportHandlerTest { + private static final LogProvider LOG_PROVIDER = NullLogProvider.getInstance(); + private static final BoltMessageLogging BOLT_LOGGING = BoltMessageLogging.none(); + @Test public void shouldCloseProtocolOnChannelInactive() throws Throwable { @@ -126,7 +129,8 @@ public void logsAndClosesProtocolOnUnexpectedExceptions() throws Throwable ChannelHandlerContext ctx = channelHandlerContextMock(); AssertableLogProvider logging = new AssertableLogProvider(); - SocketTransportHandler handler = new SocketTransportHandler( protocolChooser( machine ), logging, BoltMessageLog.getInstance() ); + BoltHandshakeProtocolHandler protocolChooser = protocolChooser( machine ); + SocketTransportHandler handler = new SocketTransportHandler( protocolChooser, logging, BOLT_LOGGING ); // And Given a session has been established handler.channelRead( ctx, handshake() ); @@ -147,7 +151,8 @@ public void logsAndClosesContextWhenProtocolNotInitializedOnUnexpectedExceptions // Given ChannelHandlerContext context = mock( ChannelHandlerContext.class ); AssertableLogProvider logging = new AssertableLogProvider(); - SocketTransportHandler handler = new SocketTransportHandler( mock( BoltHandshakeProtocolHandler.class ), logging, BoltMessageLog.getInstance() ); + SocketTransportHandler handler = new SocketTransportHandler( mock( BoltHandshakeProtocolHandler.class ), + logging, BOLT_LOGGING ); // When Throwable cause = new Throwable( "Oh no!" ); @@ -167,7 +172,7 @@ public void shouldInitializeProtocolOnFirstMessage() throws Exception BoltHandshakeProtocolHandler chooser = protocolChooser( machine ); ChannelHandlerContext context = channelHandlerContextMock(); - SocketTransportHandler handler = new SocketTransportHandler( chooser, NullLogProvider.getInstance(), BoltMessageLog.getInstance() ); + SocketTransportHandler handler = new SocketTransportHandler( chooser, LOG_PROVIDER, BOLT_LOGGING ); handler.channelRead( context, handshake() ); BoltMessagingProtocolHandler protocol1 = chooser.chosenProtocol(); @@ -178,10 +183,9 @@ public void shouldInitializeProtocolOnFirstMessage() throws Exception assertSame( protocol1, protocol2 ); } - private static SocketTransportHandler newSocketTransportHandler( BoltHandshakeProtocolHandler boltHandshakeProtocolHandler ) + private static SocketTransportHandler newSocketTransportHandler( BoltHandshakeProtocolHandler handler ) { - return new SocketTransportHandler( boltHandshakeProtocolHandler, NullLogProvider.getInstance(), - BoltMessageLog.getInstance() ); + return new SocketTransportHandler( handler, LOG_PROVIDER, BOLT_LOGGING ); } private static ChannelHandlerContext channelHandlerContextMock() diff --git a/community/common/src/main/java/org/neo4j/scheduler/JobScheduler.java b/community/common/src/main/java/org/neo4j/scheduler/JobScheduler.java index c627ef6175930..13379ea7d0e90 100644 --- a/community/common/src/main/java/org/neo4j/scheduler/JobScheduler.java +++ b/community/common/src/main/java/org/neo4j/scheduler/JobScheduler.java @@ -119,6 +119,11 @@ class Groups */ public static final Group queryLogRotation = new Group( "queryLogRotation" ); + /** + * Rotates bolt message logs + */ + public static final Group boltLogRotation = new Group( "BoltLogRotation" ); + /** * Checkpoint and store flush */ diff --git a/community/kernel/src/main/java/org/neo4j/graphdb/factory/GraphDatabaseSettings.java b/community/kernel/src/main/java/org/neo4j/graphdb/factory/GraphDatabaseSettings.java index 7d23a0cc94f6e..dbf14ef54e259 100644 --- a/community/kernel/src/main/java/org/neo4j/graphdb/factory/GraphDatabaseSettings.java +++ b/community/kernel/src/main/java/org/neo4j/graphdb/factory/GraphDatabaseSettings.java @@ -651,6 +651,14 @@ public enum LabelIndex public static final Setting default_advertised_address = setting( "dbms.connectors.default_advertised_address", STRING, "localhost" ); + @Internal + public static final Setting bolt_logging_enabled = setting( "unsupported.dbms.logs.bolt.enabled", + BOOLEAN, FALSE ); + + @Internal + public static final Setting bolt_log_filename = derivedSetting( "unsupported.dbms.logs.bolt.path", + GraphDatabaseSettings.logs_directory, logsDir -> new File( logsDir, "bolt.log" ), PATH ); + @Description( "Create an archive of an index before re-creating it if failing to load on startup." ) @Internal public static final Setting archive_failed_index = setting( diff --git a/integrationtests/src/test/java/org/neo4j/bolt/BoltFailuresIT.java b/integrationtests/src/test/java/org/neo4j/bolt/BoltFailuresIT.java index e074668a69fb6..b4b51a798c330 100644 --- a/integrationtests/src/test/java/org/neo4j/bolt/BoltFailuresIT.java +++ b/integrationtests/src/test/java/org/neo4j/bolt/BoltFailuresIT.java @@ -32,8 +32,8 @@ import org.neo4j.bolt.v1.runtime.BoltFactory; import org.neo4j.bolt.v1.runtime.MonitoredWorkerFactory.SessionMonitor; import org.neo4j.bolt.v1.runtime.WorkerFactory; -//import org.neo4j.bolt.v1.transport.BoltProtocolV1; import org.neo4j.driver.v1.Config; +import org.neo4j.bolt.v1.transport.BoltMessagingProtocolV1Handler; import org.neo4j.driver.v1.Driver; import org.neo4j.driver.v1.GraphDatabase; import org.neo4j.driver.v1.Session; @@ -62,7 +62,6 @@ import static org.hamcrest.Matchers.startsWith; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; -import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyObject; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -207,9 +206,9 @@ public void boltServerLogsRealErrorWhenDriverIsClosedWithRunningTransactions() t awaitNumberOfActiveQueriesToBe( 0 ); // verify that closing of the driver resulted in transaction termination on the server and correct log message -// internalLogProvider.assertAtLeastOnce( inLog( BoltProtocolV1.class ).warn( -// startsWith( "Unable to send error back to the client" ), -// instanceOf( TransactionTerminatedException.class ) ) ); + internalLogProvider.assertAtLeastOnce( inLog( BoltMessagingProtocolV1Handler.class ).warn( + startsWith( "Unable to send error back to the client" ), + instanceOf( TransactionTerminatedException.class ) ) ); } private void throwsWhenInitMessageFails( Consumer monitorSetup, diff --git a/integrationtests/src/test/java/org/neo4j/bolt/BoltMessageLoggingIT.java b/integrationtests/src/test/java/org/neo4j/bolt/BoltMessageLoggingIT.java new file mode 100644 index 0000000000000..b246ea8a031ab --- /dev/null +++ b/integrationtests/src/test/java/org/neo4j/bolt/BoltMessageLoggingIT.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt; + +import org.apache.commons.io.IOUtils; +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.net.URI; + +import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.GraphDatabase; +import org.neo4j.driver.v1.Session; +import org.neo4j.graphdb.factory.GraphDatabaseBuilder; +import org.neo4j.graphdb.factory.GraphDatabaseFactory; +import org.neo4j.helpers.HostnamePort; +import org.neo4j.kernel.configuration.BoltConnector; +import org.neo4j.kernel.configuration.Config; +import org.neo4j.kernel.configuration.ConnectorPortRegister; +import org.neo4j.kernel.internal.GraphDatabaseAPI; +import org.neo4j.test.TestGraphDatabaseFactory; +import org.neo4j.test.rule.DatabaseRule; +import org.neo4j.test.rule.ImpermanentDatabaseRule; +import org.neo4j.test.rule.fs.EphemeralFileSystemRule; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.neo4j.graphdb.factory.GraphDatabaseSettings.auth_enabled; +import static org.neo4j.graphdb.factory.GraphDatabaseSettings.bolt_log_filename; +import static org.neo4j.graphdb.factory.GraphDatabaseSettings.bolt_logging_enabled; +import static org.neo4j.kernel.configuration.BoltConnector.EncryptionLevel.DISABLED; +import static org.neo4j.kernel.configuration.Connector.ConnectorType.BOLT; +import static org.neo4j.kernel.configuration.Settings.FALSE; +import static org.neo4j.kernel.configuration.Settings.TRUE; + +public class BoltMessageLoggingIT +{ + private static final String CONNECTOR_KEY = "bolt"; + + @Rule + public final EphemeralFileSystemRule fs = new EphemeralFileSystemRule(); + + @Rule + public final DatabaseRule db = new ImpermanentDatabaseRule() + { + @Override + protected void configure( GraphDatabaseFactory databaseFactory ) + { + super.configure( databaseFactory ); + ((TestGraphDatabaseFactory) databaseFactory).setFileSystem( fs ); + } + + @Override + protected void configure( GraphDatabaseBuilder builder ) + { + super.configure( builder ); + builder.setConfig( auth_enabled, "false" ); + builder.setConfig( new BoltConnector( CONNECTOR_KEY ).type, BOLT.toString() ); + builder.setConfig( new BoltConnector( CONNECTOR_KEY ).enabled, TRUE ); + builder.setConfig( new BoltConnector( CONNECTOR_KEY ).listen_address, "localhost:0" ); + builder.setConfig( new BoltConnector( CONNECTOR_KEY ).encryption_level, DISABLED.toString() ); + } + }.startLazily(); + + private Driver driver; + + @After + public void closeDriver() throws Exception + { + if ( driver != null ) + { + driver.close(); + } + } + + @Test + public void shouldWriteToDefaultFileWhenEnabled() throws IOException + { + db.setConfig( bolt_logging_enabled, TRUE ); + db.ensureStarted(); + driver = newDriver(); + + File boltLogFile = config().get( bolt_log_filename ); + assertBoltLogIsWritten( boltLogFile ); + } + + @Test + public void shouldWriteNothingWhenDisabled() + { + db.setConfig( bolt_logging_enabled, FALSE ); + db.ensureStarted(); + driver = newDriver(); + + File boltLogFile = config().get( bolt_log_filename ); + assertFalse( fs.fileExists( boltLogFile ) ); + + try ( Session session = driver.session() ) + { + session.run( "CREATE ()" ).consume(); + } + + assertFalse( fs.fileExists( boltLogFile ) ); + } + + @Test + public void shouldWriteToCustomFileWhenConfigured() throws IOException + { + String customBoltLogFileName = "/tmp/my_bolt.log"; + File customBoltLogFile = new File( customBoltLogFileName ); + + db.setConfig( bolt_logging_enabled, TRUE ); + db.setConfig( bolt_log_filename, customBoltLogFileName ); + db.ensureStarted(); + driver = newDriver(); + + assertBoltLogIsWritten( customBoltLogFile ); + } + + private void assertBoltLogIsWritten( File boltLogFile ) throws IOException + { + assertTrue( fs.fileExists( boltLogFile ) ); + + String query = "CREATE (n:Person {name: 'Beta Ray Bill'}) RETURN 42"; + try ( Session session = driver.session() ) + { + session.run( query ).consume(); + } + + String contents = readFile( boltLogFile ); + assertThat( contents, containsString( "C: RUN " + query + " {}" ) ); + assertThat( contents, containsString( "S: RECORD [42]" ) ); + } + + private String readFile( File file ) throws IOException + { + return IOUtils.toString( fs.openAsInputStream( file ) ); + } + + private Config config() + { + return resolveDependency( Config.class ); + } + + private Driver newDriver() + { + org.neo4j.driver.v1.Config driverConfig = org.neo4j.driver.v1.Config.build(). + withEncryptionLevel( org.neo4j.driver.v1.Config.EncryptionLevel.NONE ) + .toConfig(); + + return GraphDatabase.driver( boltUri(), driverConfig ); + } + + private URI boltUri() + { + HostnamePort localAddress = resolveDependency( ConnectorPortRegister.class ).getLocalAddress( CONNECTOR_KEY ); + return URI.create( "bolt://" + localAddress ); + } + + private T resolveDependency( Class type ) + { + return graphDbApi().getDependencyResolver().resolveDependency( type ); + } + + private GraphDatabaseAPI graphDbApi() + { + return db.getGraphDatabaseAPI(); + } +}