mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
Add new "Collecting Logger", that can be activated to "collect" logs (e.g., in an internal list), for later access (e.g., by a test, to assert if things were logged).
This commit is contained in:
@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.logging;
|
||||||
|
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.Level;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** A log message, which can be "collected" by the QCollectingLogger.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class CollectedLogMessage
|
||||||
|
{
|
||||||
|
private Level level;
|
||||||
|
private String message;
|
||||||
|
private Throwable exception;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public CollectedLogMessage()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for message
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getMessage()
|
||||||
|
{
|
||||||
|
return (this.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for message
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setMessage(String message)
|
||||||
|
{
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for message
|
||||||
|
*******************************************************************************/
|
||||||
|
public CollectedLogMessage withMessage(String message)
|
||||||
|
{
|
||||||
|
this.message = message;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for exception
|
||||||
|
*******************************************************************************/
|
||||||
|
public Throwable getException()
|
||||||
|
{
|
||||||
|
return (this.exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for exception
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setException(Throwable exception)
|
||||||
|
{
|
||||||
|
this.exception = exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for exception
|
||||||
|
*******************************************************************************/
|
||||||
|
public CollectedLogMessage withException(Throwable exception)
|
||||||
|
{
|
||||||
|
this.exception = exception;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for level
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public Level getLevel()
|
||||||
|
{
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for level
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setLevel(Level level)
|
||||||
|
{
|
||||||
|
this.level = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for level
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public CollectedLogMessage withLevel(Level level)
|
||||||
|
{
|
||||||
|
this.level = level;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public JSONObject getMessageAsJSONObject() throws JSONException
|
||||||
|
{
|
||||||
|
return (new JSONObject(getMessage()));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,155 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.logging;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
import org.apache.logging.log4j.Level;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.apache.logging.log4j.Marker;
|
||||||
|
import org.apache.logging.log4j.message.Message;
|
||||||
|
import org.apache.logging.log4j.message.ObjectMessage;
|
||||||
|
import org.apache.logging.log4j.message.SimpleMessageFactory;
|
||||||
|
import org.apache.logging.log4j.simple.SimpleLogger;
|
||||||
|
import org.apache.logging.log4j.util.PropertiesUtil;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** QQQ log4j implementation, used within a QLogger, to "collect" log messages
|
||||||
|
** in an internal list - the idea being - for tests, to assert that logs happened.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class QCollectingLogger extends SimpleLogger
|
||||||
|
{
|
||||||
|
private List<CollectedLogMessage> collectedMessages = new ArrayList<>();
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// just in case one of these gets activated, and left on, put a limit on how many messages we'll collect //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
private int capacity = 100;
|
||||||
|
|
||||||
|
private Logger logger;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QCollectingLogger(Logger logger)
|
||||||
|
{
|
||||||
|
super(logger.getName(), logger.getLevel(), false, false, true, false, "", new SimpleMessageFactory(), new PropertiesUtil(new Properties()), System.out);
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void logMessage(String fqcn, Level level, Marker marker, Message message, Throwable throwable)
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////
|
||||||
|
// add this log message to our collection //
|
||||||
|
////////////////////////////////////////////
|
||||||
|
collectedMessages.add(new CollectedLogMessage()
|
||||||
|
.withLevel(level)
|
||||||
|
.withMessage(message.getFormattedMessage())
|
||||||
|
.withException(throwable));
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if we've gone over our capacity, remove the 1st entry until we're back at capacity //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
while(collectedMessages.size() > capacity)
|
||||||
|
{
|
||||||
|
collectedMessages.remove(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
// update the message that we log to indicate that we collected it. //
|
||||||
|
// if it looks like JSON, insert as a name:value pair; else text. //
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
String formattedMessage = message.getFormattedMessage();
|
||||||
|
String updatedMessage;
|
||||||
|
if(formattedMessage.startsWith("{"))
|
||||||
|
{
|
||||||
|
updatedMessage = """
|
||||||
|
{"collected":true,""" + formattedMessage.substring(1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
updatedMessage = "[Collected] " + formattedMessage;
|
||||||
|
}
|
||||||
|
ObjectMessage myMessage = new ObjectMessage(updatedMessage);
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// log the message with the original log4j logger, with our slightly updated message //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
logger.logMessage(level, marker, fqcn, null, myMessage, throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for logger
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setLogger(Logger logger)
|
||||||
|
{
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for collectedMessages
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public List<CollectedLogMessage> getCollectedMessages()
|
||||||
|
{
|
||||||
|
return collectedMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void clear()
|
||||||
|
{
|
||||||
|
this.collectedMessages.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for capacity
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setCapacity(int capacity)
|
||||||
|
{
|
||||||
|
this.capacity = capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -119,6 +119,34 @@ public class QLogger
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static QCollectingLogger activateCollectingLoggerForClass(Class<?> c)
|
||||||
|
{
|
||||||
|
Logger loggerFromLogManager = LogManager.getLogger(c);
|
||||||
|
QCollectingLogger collectingLogger = new QCollectingLogger(loggerFromLogManager);
|
||||||
|
|
||||||
|
QLogger qLogger = getLogger(c);
|
||||||
|
qLogger.setLogger(collectingLogger);
|
||||||
|
|
||||||
|
return collectingLogger;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static void deactivateCollectingLoggerForClass(Class<?> c)
|
||||||
|
{
|
||||||
|
Logger loggerFromLogManager = LogManager.getLogger(c);
|
||||||
|
QLogger qLogger = getLogger(c);
|
||||||
|
qLogger.setLogger(loggerFromLogManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -518,7 +546,7 @@ public class QLogger
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private String makeJsonString(String message, Throwable t, List<LogPair> logPairList)
|
protected String makeJsonString(String message, Throwable t, List<LogPair> logPairList)
|
||||||
{
|
{
|
||||||
if(logPairList == null)
|
if(logPairList == null)
|
||||||
{
|
{
|
||||||
@ -620,4 +648,15 @@ public class QLogger
|
|||||||
exceptionList.get(0).setHasLoggedLevel(level);
|
exceptionList.get(0).setHasLoggedLevel(level);
|
||||||
return (level);
|
return (level);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for logger
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private void setLogger(Logger logger)
|
||||||
|
{
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.logging;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||||
|
import org.apache.logging.log4j.Level;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Unit test for QCollectingLogger
|
||||||
|
*******************************************************************************/
|
||||||
|
class QCollectingLoggerTest extends BaseTest
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void test()
|
||||||
|
{
|
||||||
|
ClassThatLogsThings classThatLogsThings = new ClassThatLogsThings();
|
||||||
|
classThatLogsThings.logAnInfo("1");
|
||||||
|
|
||||||
|
QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(ClassThatLogsThings.class);
|
||||||
|
classThatLogsThings.logAnInfo("2");
|
||||||
|
classThatLogsThings.logAWarn("3");
|
||||||
|
QLogger.deactivateCollectingLoggerForClass(ClassThatLogsThings.class);
|
||||||
|
|
||||||
|
classThatLogsThings.logAWarn("4");
|
||||||
|
|
||||||
|
assertEquals(2, collectingLogger.getCollectedMessages().size());
|
||||||
|
|
||||||
|
assertThat(collectingLogger.getCollectedMessages().get(0).getMessage()).contains("""
|
||||||
|
"message":"2",""");
|
||||||
|
assertEquals("2", collectingLogger.getCollectedMessages().get(0).getMessageAsJSONObject().getString("message"));
|
||||||
|
assertEquals(Level.INFO, collectingLogger.getCollectedMessages().get(0).getLevel());
|
||||||
|
|
||||||
|
assertThat(collectingLogger.getCollectedMessages().get(1).getMessage()).contains("""
|
||||||
|
"message":"3",""");
|
||||||
|
assertEquals(Level.WARN, collectingLogger.getCollectedMessages().get(1).getLevel());
|
||||||
|
assertEquals("3", collectingLogger.getCollectedMessages().get(1).getMessageAsJSONObject().getString("message"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static class ClassThatLogsThings
|
||||||
|
{
|
||||||
|
private static final QLogger LOG = QLogger.getLogger(ClassThatLogsThings.class);
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private void logAnInfo(String message)
|
||||||
|
{
|
||||||
|
LOG.info(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private void logAWarn(String message)
|
||||||
|
{
|
||||||
|
LOG.warn(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user