cancel
Showing results for 
Search instead for 
Did you mean: 

Logging in Neo4j Procedures

glilienfield
Ninja
Ninja

I have created some Neo4j procedures. I inject a Log using the @Context annotation. I pass the Log through the call chain to other methods so I can log within those methods. This works fine. I now want to unit test the methods independently, which requires a Log to be passed in the method calls. How do I create a Logger that is compatible with the neo4j Log (which is an interface) that is injected by the @Context annotation?

3 REPLIES 3

khituras
Node

I have the very same issue and question. Did you end up finding a solution?

giuseppe_villan
Graph Fellow

@glilienfield @khituras

Maybe you can try with something similar to what was done in the APOC, neo4j-apoc-procedures/CypherProceduresStorageTest.java at ad48af6a7dde19825d7fcc100fd2c0612536289d ·... .


That is:

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.neo4j.configuration.GraphDatabaseSettings;
import org.neo4j.dbms.api.DatabaseManagementService;
import org.neo4j.exceptions.KernelException;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.kernel.api.procedure.GlobalProcedures;
import org.neo4j.kernel.internal.GraphDatabaseAPI;
import org.neo4j.test.TestDatabaseManagementServiceBuilder;

import java.nio.file.Files;
import java.nio.file.Path;

import static org.junit.jupiter.api.Assertions.assertFalse;


public class CypherProceduresStorageTest {

    @Rule
    public TemporaryFolder STORE_DIR = new TemporaryFolder();

    private GraphDatabaseService db;

    @Before
    public void setUp() throws Exception {
        DatabaseManagementService databaseManagementService = new TestDatabaseManagementServiceBuilder(STORE_DIR.getRoot().toPath()).build();
        db = databaseManagementService.database(GraphDatabaseSettings.DEFAULT_DATABASE_NAME);
        
        // register the procedure class
        registerProcedure(db, CypherProcedures.class);
    }

    @Test
    public void testIssue1744() throws Exception {
        // call my procedure
        db.executeTransactionally("CALL my.procedure");
        
        // get entire file as string
        final String logFileContent = Files.readString(Path.of(STORE_DIR.getRoot().getPath(), "logs" , "debug.log"));
        
        // assertions...
        assertFalse(logFileContent.contains("Could not register function: custom.vantagepoint_within_area"));
    }

    // from https://github.com/neo4j-contrib/neo4j-apoc-procedures/blob/4.3/test-utils/src/main/java/apoc/util/TestUtil.java
    public static void registerProcedure(GraphDatabaseService db, Class<?>...procedures) {
        GlobalProcedures globalProcedures = ((GraphDatabaseAPI) db).getDependencyResolver().resolveDependency(GlobalProcedures.class);
        for (Class<?> procedure : procedures) {
            try {
                globalProcedures.registerProcedure(procedure, true);
                globalProcedures.registerFunction(procedure, true);
                globalProcedures.registerAggregationFunction(procedure, true);
            } catch (KernelException e) {
                throw new RuntimeException("while registering " + procedure, e);
            }
        }
    }
}

khituras
Node

Thanks for the response. We - at least I - actually need a Log object pass it to the methods we test. We talk about unit testing instead of integration testing here. But perhaps this is not the intended way to do it in Neo4j?
I actually had some success in the meantime. I managed to create a LogProvider and get a logger from it like so:

Log4jLogProvider log4jLogProvider = new Log4jLogProvider(LogConfig.createBuilder(System.out, Level.INFO)
                .withFormat(PLAIN)
                .withCategory(false)
                .build());
log = log4jLogProvider.getLog(ExtensionClass.class);

In this way, the logger can now be passed to the tested methods or the extension as a whole.