JezK
Edit File: LoggingTest.cpp
#include "TestSupport.h" #include <Logging.h> #include <MessageClient.h> #include <LoggingAgent/LoggingServer.h> #include <Utils/MessageIO.h> #include <boost/bind.hpp> #include <boost/shared_ptr.hpp> #include <oxt/thread.hpp> #include <set> using namespace Passenger; using namespace std; using namespace oxt; namespace tut { struct LoggingTest { static const unsigned long long YESTERDAY = 1263299017000000ull; // January 12, 2009, 12:23:37 UTC static const unsigned long long TODAY = 1263385422000000ull; // January 13, 2009, 12:23:42 UTC static const unsigned long long TOMORROW = 1263471822000000ull; // January 14, 2009, 12:23:42 UTC #define FOOBAR_MD5 "3858f62230ac3c915f300c664312c63f" #define LOCALHOST_MD5 "421aa90e079fa326b6494f812ad13e79" #define REMOTEHOST_MD5 "2c18e486683a3db1e645ad8523223b72" #define FOOBAR_LOCALHOST_PREFIX FOOBAR_MD5 "/" LOCALHOST_MD5 #define FOOBAR_REMOTEHOST_PREFIX FOOBAR_MD5 "/" REMOTEHOST_MD5 #define TODAY_TXN_ID "cjb8n-abcd" #define TODAY_TIMESTAMP_STR "cftz90m3k0" ServerInstanceDirPtr serverInstanceDir; ServerInstanceDir::GenerationPtr generation; string socketFilename; string socketAddress; string loggingDir; AccountsDatabasePtr accountsDatabase; ev::dynamic_loop eventLoop; FileDescriptor serverFd; LoggingServerPtr server; shared_ptr<oxt::thread> serverThread; AnalyticsLoggerPtr logger, logger2, logger3, logger4; LoggingTest() { createServerInstanceDirAndGeneration(serverInstanceDir, generation); socketFilename = generation->getPath() + "/logging.socket"; socketAddress = "unix:" + socketFilename; loggingDir = generation->getPath() + "/logs"; accountsDatabase = ptr(new AccountsDatabase()); accountsDatabase->add("test", "1234", false); setLogLevel(-1); startLoggingServer(); logger = ptr(new AnalyticsLogger(socketAddress, "test", "1234", "localhost")); logger2 = ptr(new AnalyticsLogger(socketAddress, "test", "1234", "localhost")); logger3 = ptr(new AnalyticsLogger(socketAddress, "test", "1234", "localhost")); logger4 = ptr(new AnalyticsLogger(socketAddress, "test", "1234", "localhost")); } ~LoggingTest() { stopLoggingServer(); SystemTime::releaseAll(); setLogLevel(0); } void startLoggingServer(const function<void ()> &initFunc = function<void ()>()) { serverFd = createUnixServer(socketFilename.c_str()); server = ptr(new LoggingServer(eventLoop, serverFd, accountsDatabase, loggingDir)); if (initFunc) { initFunc(); } serverThread = ptr(new oxt::thread( boost::bind(&LoggingTest::runLoop, this) )); } void stopLoggingServer(bool destroy = true) { if (serverThread != NULL) { MessageClient client; client.connect(socketAddress, "test", "1234"); client.write("exit", "immediately", NULL); joinLoggingServer(destroy); } } void joinLoggingServer(bool destroy = true) { serverThread->join(); serverThread.reset(); if (destroy) { server.reset(); } unlink(socketFilename.c_str()); } void runLoop() { eventLoop.loop(); serverFd.close(); } string timestampString(unsigned long long timestamp) { char str[2 * sizeof(unsigned long long) + 1]; integerToHexatri<unsigned long long>(timestamp, str); return str; } MessageClient createConnection(bool sendInitCommand = true) { MessageClient client; vector<string> args; client.connect(socketAddress, "test", "1234"); if (sendInitCommand) { client.write("init", "localhost", NULL); client.read(args); } return client; } }; DEFINE_TEST_GROUP(LoggingTest); /*********** Logging interface tests ***********/ TEST_METHOD(1) { // Test logging of new transaction. SystemTime::forceAll(YESTERDAY); AnalyticsLogPtr log = logger->newTransaction("foobar"); log->message("hello"); log->message("world"); log->flushToDiskAfterClose(true); ensure(!logger->isNull()); ensure(!log->isNull()); log.reset(); string data = readAll(loggingDir + "/1/" FOOBAR_LOCALHOST_PREFIX "/requests/2010/01/12/12/log.txt"); ensure(data.find("hello\n") != string::npos); ensure(data.find("world\n") != string::npos); } TEST_METHOD(2) { // Test logging of existing transaction. SystemTime::forceAll(YESTERDAY); AnalyticsLogPtr log = logger->newTransaction("foobar"); log->message("message 1"); log->flushToDiskAfterClose(true); AnalyticsLogPtr log2 = logger2->continueTransaction(log->getTxnId(), log->getGroupName(), log->getCategory()); log2->message("message 2"); log2->flushToDiskAfterClose(true); log.reset(); log2.reset(); string data = readAll(loggingDir + "/1/" FOOBAR_LOCALHOST_PREFIX "/requests/2010/01/12/12/log.txt"); ensure("(1)", data.find("message 1\n") != string::npos); ensure("(2)", data.find("message 2\n") != string::npos); } TEST_METHOD(3) { // Test logging with different points in time. SystemTime::forceAll(YESTERDAY); AnalyticsLogPtr log = logger->newTransaction("foobar"); log->message("message 1"); SystemTime::forceAll(TODAY); log->message("message 2"); log->flushToDiskAfterClose(true); SystemTime::forceAll(TOMORROW); AnalyticsLogPtr log2 = logger2->continueTransaction(log->getTxnId(), log->getGroupName(), log->getCategory()); log2->message("message 3"); log2->flushToDiskAfterClose(true); AnalyticsLogPtr log3 = logger3->newTransaction("foobar"); log3->message("message 4"); log3->flushToDiskAfterClose(true); log.reset(); log2.reset(); log3.reset(); string yesterdayData = readAll(loggingDir + "/1/" FOOBAR_LOCALHOST_PREFIX "/requests/2010/01/12/12/log.txt"); string tomorrowData = readAll(loggingDir + "/1/" FOOBAR_LOCALHOST_PREFIX "/requests/2010/01/14/12/log.txt"); ensure("(1)", yesterdayData.find(timestampString(YESTERDAY) + " 1 message 1\n") != string::npos); ensure("(2)", yesterdayData.find(timestampString(TODAY) + " 2 message 2\n") != string::npos); ensure("(3)", yesterdayData.find(timestampString(TOMORROW) + " 4 message 3\n") != string::npos); ensure("(4)", tomorrowData.find(timestampString(TOMORROW) + " 1 message 4\n") != string::npos); } TEST_METHOD(4) { // newTransaction() and continueTransaction() write an ATTACH message // to the log file, while AnalyticsLogPtr writes a DETACH message upon // destruction. SystemTime::forceAll(YESTERDAY); AnalyticsLogPtr log = logger->newTransaction("foobar"); SystemTime::forceAll(TODAY); AnalyticsLogPtr log2 = logger2->continueTransaction(log->getTxnId(), log->getGroupName(), log->getCategory()); log2->flushToDiskAfterClose(true); log2.reset(); SystemTime::forceAll(TOMORROW); log->flushToDiskAfterClose(true); log.reset(); string data = readAll(loggingDir + "/1/" FOOBAR_LOCALHOST_PREFIX "/requests/2010/01/12/12/log.txt"); ensure("(1)", data.find(timestampString(YESTERDAY) + " 0 ATTACH\n") != string::npos); ensure("(2)", data.find(timestampString(TODAY) + " 1 ATTACH\n") != string::npos); ensure("(3)", data.find(timestampString(TODAY) + " 2 DETACH\n") != string::npos); ensure("(4)", data.find(timestampString(TOMORROW) + " 3 DETACH\n") != string::npos); } TEST_METHOD(5) { // newTransaction() generates a new ID, while continueTransaction() // reuses the ID. AnalyticsLogPtr log = logger->newTransaction("foobar"); AnalyticsLogPtr log2 = logger2->newTransaction("foobar"); AnalyticsLogPtr log3 = logger3->continueTransaction(log->getTxnId(), log->getGroupName(), log->getCategory()); AnalyticsLogPtr log4 = logger4->continueTransaction(log2->getTxnId(), log2->getGroupName(), log2->getCategory()); ensure_equals(log->getTxnId(), log3->getTxnId()); ensure_equals(log2->getTxnId(), log4->getTxnId()); ensure(log->getTxnId() != log2->getTxnId()); } TEST_METHOD(6) { // An empty AnalyticsLog doesn't do anything. AnalyticsLog log; ensure(log.isNull()); log.message("hello world"); ensure_equals(getFileType(loggingDir), FT_NONEXISTANT); } TEST_METHOD(7) { // An empty AnalyticsLogger doesn't do anything. AnalyticsLogger logger; ensure(logger.isNull()); AnalyticsLogPtr log = logger.newTransaction("foo"); ensure(log->isNull()); log->message("hello world"); ensure_equals(getFileType(loggingDir), FT_NONEXISTANT); } TEST_METHOD(8) { // It creates a file group_name.txt under the group directory. AnalyticsLogPtr log = logger->newTransaction("foobar"); log->flushToDiskAfterClose(true); log.reset(); string data = readAll(loggingDir + "/1/" FOOBAR_MD5 "/group_name.txt"); ensure_equals(data, "foobar"); } TEST_METHOD(9) { // It creates a file node_name.txt under the node directory. AnalyticsLogPtr log = logger->newTransaction("foobar"); log->flushToDiskAfterClose(true); log.reset(); string data = readAll(loggingDir + "/1/" FOOBAR_LOCALHOST_PREFIX "/node_name.txt"); ensure_equals(data, "localhost"); } TEST_METHOD(11) { // newTransaction() does not reconnect to the server for a short // period of time if connecting failed logger->setReconnectTimeout(60 * 1000000); logger->setMaxConnectTries(1); SystemTime::forceAll(TODAY); stopLoggingServer(); ensure(logger->newTransaction("foobar")->isNull()); SystemTime::forceAll(TODAY + 30 * 1000000); startLoggingServer(); ensure(logger->newTransaction("foobar")->isNull()); SystemTime::forceAll(TODAY + 61 * 1000000); ensure(!logger->newTransaction("foobar")->isNull()); } TEST_METHOD(12) { // If the logging server crashed and was restarted then // newTransaction() and continueTransaction() print a warning and return // a null log object. One of the next newTransaction()/continueTransaction() // calls will reestablish the connection when the connection timeout // has passed. SystemTime::forceAll(TODAY); AnalyticsLogPtr log, log2; log = logger->newTransaction("foobar"); logger2->continueTransaction(log->getTxnId(), "foobar"); stopLoggingServer(); startLoggingServer(); log = logger->newTransaction("foobar"); ensure("(1)", log->isNull()); log2 = logger2->continueTransaction("some-id", "foobar"); ensure("(2)", log2->isNull()); SystemTime::forceAll(TODAY + 60000000); log = logger->newTransaction("foobar"); ensure("(3)", !log->isNull()); log2 = logger2->continueTransaction(log->getTxnId(), "foobar"); ensure("(4)", !log2->isNull()); log2->message("hello"); log2->flushToDiskAfterClose(true); log.reset(); log2.reset(); string data = readAll(loggingDir + "/1/" FOOBAR_LOCALHOST_PREFIX "/requests/2010/01/13/12/log.txt"); ensure("(5)", data.find("hello\n") != string::npos); } TEST_METHOD(13) { // continueTransaction() does not reconnect to the server for a short // period of time if connecting failed logger->setReconnectTimeout(60 * 1000000); logger->setMaxConnectTries(1); logger2->setReconnectTimeout(60 * 1000000); logger2->setMaxConnectTries(1); SystemTime::forceAll(TODAY); AnalyticsLogPtr log = logger->newTransaction("foobar"); logger2->continueTransaction(log->getTxnId(), "foobar"); stopLoggingServer(); ensure(logger2->continueTransaction(log->getTxnId(), "foobar")->isNull()); SystemTime::forceAll(TODAY + 30 * 1000000); startLoggingServer(); ensure(logger2->continueTransaction(log->getTxnId(), "foobar")->isNull()); SystemTime::forceAll(TODAY + 61 * 1000000); ensure(!logger2->continueTransaction(log->getTxnId(), "foobar")->isNull()); } TEST_METHOD(14) { // If a client disconnects from the logging server then all its // transactions that are no longer referenced and have crash protection enabled // will be closed and written to to the sink. MessageClient client1 = createConnection(); MessageClient client2 = createConnection(); MessageClient client3 = createConnection(); vector<string> args; string filename = loggingDir + "/1/" FOOBAR_LOCALHOST_PREFIX "/requests/2010/01/13/12/log.txt"; SystemTime::forceAll(TODAY); client1.write("openTransaction", TODAY_TXN_ID, "foobar", "", "requests", TODAY_TIMESTAMP_STR, "", "true", NULL); client2.write("openTransaction", TODAY_TXN_ID, "foobar", "", "requests", TODAY_TIMESTAMP_STR, "", "true", NULL); client2.write("flush", NULL); client2.read(args); client2.disconnect(); SHOULD_NEVER_HAPPEN(100, result = fileExists(filename) && !readAll(filename).empty(); ); client1.disconnect(); client3.write("flush", NULL); client3.read(args); EVENTUALLY(5, result = fileExists(filename) && !readAll(filename).empty(); ); } TEST_METHOD(15) { // If a client disconnects from the logging server then all its // transactions that are no longer referenced and don't have crash // protection enabled will be closed and discarded. MessageClient client1 = createConnection(); MessageClient client2 = createConnection(); MessageClient client3 = createConnection(); vector<string> args; string filename = loggingDir + "/1/" FOOBAR_LOCALHOST_PREFIX "/requests/2010/01/13/12/log.txt"; SystemTime::forceAll(TODAY); client1.write("openTransaction", TODAY_TXN_ID, "foobar", "", "requests", TODAY_TIMESTAMP_STR, "", "false", NULL); client2.write("openTransaction", TODAY_TXN_ID, "foobar", "", "requests", TODAY_TIMESTAMP_STR, "", "false", NULL); client2.write("flush", NULL); client2.read(args); client2.disconnect(); client1.disconnect(); client3.write("flush", NULL); client3.read(args); SHOULD_NEVER_HAPPEN(500, result = fileExists(filename) && !readAll(filename).empty(); ); } TEST_METHOD(16) { // Upon server shutdown, all transaction that have crash protection enabled // will be closed and written to to the sink. MessageClient client1 = createConnection(); MessageClient client2 = createConnection(); vector<string> args; string filename = loggingDir + "/1/" FOOBAR_LOCALHOST_PREFIX "/requests/2010/01/13/12/log.txt"; SystemTime::forceAll(TODAY); client1.write("openTransaction", TODAY_TXN_ID, "foobar", "", "requests", TODAY_TIMESTAMP_STR, "", "true", NULL); client2.write("openTransaction", TODAY_TXN_ID, "foobar", "", "requests", TODAY_TIMESTAMP_STR, "", "true", NULL); client2.write("flush", NULL); client2.read(args); stopLoggingServer(); EVENTUALLY(5, result = fileExists(filename) && !readAll(filename).empty(); ); } TEST_METHOD(17) { // Upon server shutdown, all transaction that don't have crash protection // enabled will be discarded. MessageClient client1 = createConnection(); MessageClient client2 = createConnection(); vector<string> args; string filename = loggingDir + "/1/" FOOBAR_LOCALHOST_PREFIX "/requests/2010/01/13/12/log.txt"; SystemTime::forceAll(TODAY); client1.write("openTransaction", TODAY_TXN_ID, "foobar", "", "requests", TODAY_TIMESTAMP_STR, "", "false", NULL); client2.write("openTransaction", TODAY_TXN_ID, "foobar", "", "requests", TODAY_TIMESTAMP_STR, "", "false", NULL); client2.write("flush", NULL); client2.read(args); stopLoggingServer(); SHOULD_NEVER_HAPPEN(200, result = fileExists(filename) && !readAll(filename).empty(); ); } TEST_METHOD(18) { // Test DataStoreId { // Empty construction. DataStoreId id; ensure_equals(id.getGroupName(), ""); ensure_equals(id.getNodeName(), ""); ensure_equals(id.getCategory(), ""); } { // Normal construction. DataStoreId id("ab", "cd", "ef"); ensure_equals(id.getGroupName(), "ab"); ensure_equals(id.getNodeName(), "cd"); ensure_equals(id.getCategory(), "ef"); } { // Copy constructor. DataStoreId id("ab", "cd", "ef"); DataStoreId id2(id); ensure_equals(id2.getGroupName(), "ab"); ensure_equals(id2.getNodeName(), "cd"); ensure_equals(id2.getCategory(), "ef"); } { // Assignment operator. DataStoreId id("ab", "cd", "ef"); DataStoreId id2; id2 = id; ensure_equals(id2.getGroupName(), "ab"); ensure_equals(id2.getNodeName(), "cd"); ensure_equals(id2.getCategory(), "ef"); DataStoreId id3("gh", "ij", "kl"); id3 = id; ensure_equals(id3.getGroupName(), "ab"); ensure_equals(id3.getNodeName(), "cd"); ensure_equals(id3.getCategory(), "ef"); } { // < operator DataStoreId id, id2; ensure(!(id < id2)); id = DataStoreId("ab", "cd", "ef"); id2 = DataStoreId("ab", "cd", "ef"); ensure(!(id < id2)); id = DataStoreId("ab", "cd", "ef"); id2 = DataStoreId("bb", "cd", "ef"); ensure(id < id2); id = DataStoreId("ab", "cd", "ef"); id2 = DataStoreId(); ensure(id2 < id); id = DataStoreId(); id2 = DataStoreId("ab", "cd", "ef"); ensure(id < id2); } { // == operator ensure(DataStoreId() == DataStoreId()); ensure(DataStoreId("ab", "cd", "ef") == DataStoreId("ab", "cd", "ef")); ensure(!(DataStoreId("ab", "cd", "ef") == DataStoreId())); ensure(!(DataStoreId("ab", "cd", "ef") == DataStoreId("ab", "cd", "e"))); ensure(!(DataStoreId("ab", "cd", "ef") == DataStoreId("ab", "c", "ef"))); ensure(!(DataStoreId("ab", "cd", "ef") == DataStoreId("a", "cd", "ef"))); } } TEST_METHOD(21) { // The server temporarily buffers data in memory. SystemTime::forceAll(YESTERDAY); AnalyticsLogPtr log = logger->newTransaction("foobar"); log->message("hello world"); log.reset(); // Give server some time to process these commands. usleep(20000); string filename = loggingDir + "/1/" FOOBAR_LOCALHOST_PREFIX "/requests/2010/01/12/12/log.txt"; struct stat buf; ensure_equals(stat(filename.c_str(), &buf), 0); ensure_equals(buf.st_size, (off_t) 0); } TEST_METHOD(22) { // The destructor flushes all data. SystemTime::forceAll(YESTERDAY); AnalyticsLogPtr log = logger->newTransaction("foobar"); log->message("hello world"); log.reset(); stopLoggingServer(); string filename = loggingDir + "/1/" FOOBAR_LOCALHOST_PREFIX "/requests/2010/01/12/12/log.txt"; struct stat buf; ensure_equals(stat(filename.c_str(), &buf), 0); ensure(buf.st_size > 0); } TEST_METHOD(23) { // The 'flush' command flushes all data. SystemTime::forceAll(YESTERDAY); AnalyticsLogPtr log = logger->newTransaction("foobar"); log->message("hello world"); log.reset(); vector<string> args; writeArrayMessage(logger->getConnection(), "flush", NULL); ensure(readArrayMessage(logger->getConnection(), args)); ensure_equals(args.size(), 1u); ensure_equals(args[0], "ok"); string filename = loggingDir + "/1/" FOOBAR_LOCALHOST_PREFIX "/requests/2010/01/12/12/log.txt"; struct stat buf; ensure_equals(stat(filename.c_str(), &buf), 0); ensure(buf.st_size > 0); } TEST_METHOD(24) { // A transaction's data is not written out by the server // until the transaction is fully closed. SystemTime::forceAll(YESTERDAY); vector<string> args; AnalyticsLogPtr log = logger->newTransaction("foobar"); log->message("hello world"); AnalyticsLogPtr log2 = logger2->continueTransaction(log->getTxnId(), log->getGroupName(), log->getCategory()); log2->message("message 2"); log2.reset(); writeArrayMessage(logger->getConnection(), "flush", NULL); ensure(readArrayMessage(logger->getConnection(), args)); writeArrayMessage(logger2->getConnection(), "flush", NULL); ensure(readArrayMessage(logger2->getConnection(), args)); string filename = loggingDir + "/1/" FOOBAR_LOCALHOST_PREFIX "/requests/2010/01/12/12/log.txt"; struct stat buf; ensure_equals(stat(filename.c_str(), &buf), 0); ensure_equals(buf.st_size, (off_t) 0); } TEST_METHOD(25) { // The 'exit' command causes the logging server to exit some time after // the last client has disconnected. New clients are still accepted // as long as the server hasn't exited. SystemTime::forceAll(YESTERDAY); vector<string> args; MessageClient client = createConnection(); MessageClient client2 = createConnection(); client2.write("exit", NULL); ensure("(1)", client2.read(args)); ensure_equals(args.size(), 1u); ensure_equals(args[0], "Passed security"); ensure("(2)", client2.read(args)); ensure_equals(args.size(), 1u); ensure_equals(args[0], "exit command received"); client2.disconnect(); // Not exited yet: there is still a client. client2 = createConnection(); client2.write("ping", NULL); ensure("(3)", client2.read(args)); client2.disconnect(); client.disconnect(); setLogLevel(-2); usleep(25000); // Give server some time to process the connection closes. // No clients now, but we can still connect because the timeout // hasn't passed yet. SystemTime::forceAll(YESTERDAY + 1000000); SHOULD_NEVER_HAPPEN(250, try { close(connectToUnixServer(socketFilename)); result = false; } catch (const SystemException &) { result = true; } ); usleep(50000); // Give server some time to process the connection closes. // It'll be gone in a few seconds. SystemTime::forceAll(YESTERDAY + 1000000 + 5000000); usleep(100000); // Give server some time to run the timer. try { close(connectToUnixServer(socketFilename)); fail("(4)"); } catch (const SystemException &) { // Success } joinLoggingServer(); } TEST_METHOD(26) { // The 'exit semi-gracefully' command causes the logging server to // refuse new clients while exiting some time after the last client has // disconnected. SystemTime::forceAll(YESTERDAY); vector<string> args; MessageClient client = createConnection(); MessageClient client2 = createConnection(); client2.write("exit", "semi-gracefully", NULL); client2.disconnect(); // New connections are refused. client2 = createConnection(); ensure("(1)", !client2.read(args)); client.disconnect(); setLogLevel(-2); usleep(50000); // Give server some time to process the connection closes. // It'll be gone in a few seconds. SystemTime::forceAll(YESTERDAY + 1000000 + 5000000); usleep(100000); // Give server some time to run the timer. try { close(connectToUnixServer(socketFilename)); fail("(2)"); } catch (const SystemException &) { // Success } joinLoggingServer(); } TEST_METHOD(27) { // The 'exit immediately' command causes the logging server to // immediately exit. Open transactions are not automatically // closed and written out, even those with crash protection // turned on. SystemTime::forceAll(YESTERDAY); AnalyticsLogPtr log = logger->newTransaction("foobar"); log->message("hello world"); log.reset(); MessageClient client = createConnection(); client.write("exit", "immediately", NULL); client.disconnect(); // Assertion: the following doesn't block. joinLoggingServer(); } TEST_METHOD(28) { // AnalyticsLogger treats a server that's semi-gracefully exiting as // one that's refusing connections. SystemTime::forceAll(YESTERDAY); MessageClient client = createConnection(); client.write("exit", "semi-gracefully", NULL); client.disconnect(); logger->setMaxConnectTries(1); AnalyticsLogPtr log = logger->newTransaction("foobar"); ensure(log->isNull()); } TEST_METHOD(29) { // One can supply a custom node name per openTransaction command. MessageClient client1 = createConnection(); vector<string> args; string filename = loggingDir + "/1/" FOOBAR_REMOTEHOST_PREFIX "/requests/2010/01/13/12/log.txt"; SystemTime::forceAll(TODAY); client1.write("openTransaction", TODAY_TXN_ID, "foobar", "remote", "requests", TODAY_TIMESTAMP_STR, "", "true", NULL); client1.write("closeTransaction", TODAY_TXN_ID, TODAY_TIMESTAMP_STR, NULL); client1.write("flush", NULL); client1.read(args); client1.disconnect(); ensure(fileExists(filename)); } TEST_METHOD(30) { // A transaction is only written to the sink if it passes all given filters. // Test logging of new transaction. SystemTime::forceAll(YESTERDAY); AnalyticsLogPtr log = logger->newTransaction("foobar", "requests", "", "uri == \"/foo\"" "\1" "uri != \"/bar\""); log->message("URI: /foo"); log->message("transaction 1"); log->flushToDiskAfterClose(true); log.reset(); log = logger->newTransaction("foobar", "requests", "", "uri == \"/foo\"" "\1" "uri == \"/bar\""); log->message("URI: /foo"); log->message("transaction 2"); log->flushToDiskAfterClose(true); log.reset(); string data = readAll(loggingDir + "/1/" FOOBAR_LOCALHOST_PREFIX "/requests/2010/01/12/12/log.txt"); ensure("(1)", data.find("transaction 1\n") != string::npos); ensure("(2)", data.find("transaction 2\n") == string::npos); } /************************************/ }