Skip to content

Commit

Permalink
Implements writeLog BIF
Browse files Browse the repository at this point in the history
  • Loading branch information
jclausen committed Apr 10, 2024
1 parent 1707116 commit ab72978
Show file tree
Hide file tree
Showing 3 changed files with 250 additions and 4 deletions.
117 changes: 113 additions & 4 deletions src/main/java/ortus/boxlang/runtime/bifs/global/system/WriteLog.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,35 +17,144 @@
*/
package ortus.boxlang.runtime.bifs.global.system;

import java.nio.file.Paths;
import java.util.HashMap;

import org.slf4j.LoggerFactory;

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.FileAppender;
import ortus.boxlang.runtime.BoxRuntime;
import ortus.boxlang.runtime.bifs.BIF;
import ortus.boxlang.runtime.bifs.BoxBIF;
import ortus.boxlang.runtime.context.IBoxContext;
import ortus.boxlang.runtime.logging.LoggingConfigurator;
import ortus.boxlang.runtime.scopes.ArgumentsScope;
import ortus.boxlang.runtime.scopes.Key;
import ortus.boxlang.runtime.types.Argument;
import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException;

@BoxBIF
public class WriteLog extends BIF {

private final String LEVEL_TRACE = "trace";
private final String LEVEL_DEBUG = "debug";
private final String LEVEL_INFO = "info";
private final String LEVEL_WARN = "warn";
private final String LEVEL_ERROR = "error";

private final HashMap<Key, String> levelMap = new HashMap<Key, String>() {

{
put( Key.of( "Trace" ), LEVEL_TRACE );
put( Key.of( "Debug" ), LEVEL_DEBUG );
put( Key.of( "Debugging" ), LEVEL_DEBUG );
put( Key.of( "Info" ), LEVEL_INFO );
put( Key.of( "Information" ), LEVEL_INFO );
put( Key.of( "Warning" ), LEVEL_WARN );
put( Key.of( "Warn" ), LEVEL_WARN );
put( Key.of( "Error" ), LEVEL_ERROR );
put( Key.of( "Fatal" ), LEVEL_ERROR );
}
};

private final BoxRuntime instance = BoxRuntime.getInstance();

private final String logsDirectory = instance.getConfiguration().runtime.logsDirectory;

/**
* Constructor
*/
public WriteLog() {
super();
declaredArguments = new Argument[] {
// TODO: implement
new Argument( true, "string", Key.text ),
new Argument( false, "string", Key.file ),
new Argument( false, "string", Key.log, "Application" ),
new Argument( false, "string", Key.type, "Information" ),
};
}

/**
*
*
*
*
* @param context The context in which the BIF is being invoked.
* @param arguments Argument scope for the BIF.
*
* @argument.
*/
public Object _invoke( IBoxContext context, ArgumentsScope arguments ) {
// TODO: implement
String logText = arguments.getAsString( Key.text );
String file = arguments.getAsString( Key.file );
String logCategory = arguments.getAsString( Key.log );
String logLevel = arguments.getAsString( Key.type );
Key levelKey = Key.of( logLevel );

if ( !levelMap.containsKey( levelKey ) ) {
throw new BoxRuntimeException(
String.format(
"[%s] is not a valid logging level type.",
logLevel
)
);
}

Logger logger = ( ch.qos.logback.classic.Logger ) LoggerFactory.getLogger( logCategory );
FileAppender<ILoggingEvent> fileAppender = null;
try {
if ( file != null ) {
String filePath = Paths.get( logsDirectory, "/", file ).normalize().toString();
LoggerContext logContext = ( LoggerContext ) LoggerFactory.getILoggerFactory();
PatternLayoutEncoder layoutEncoder = new PatternLayoutEncoder();
layoutEncoder.setPattern( LoggingConfigurator.LOG_FORMAT );
layoutEncoder.setContext( logContext );
layoutEncoder.start();
fileAppender = new FileAppender<ILoggingEvent>();
fileAppender.setFile( filePath );
fileAppender.setEncoder( layoutEncoder );
fileAppender.setContext( logContext );
fileAppender.start();

logger.addAppender( fileAppender );
logger.setAdditive( false );
} else {
logger = ( ch.qos.logback.classic.Logger ) LoggerFactory.getLogger( logCategory );
}

switch ( levelMap.get( levelKey ) ) {
case LEVEL_TRACE : {
logger.trace( logText );
break;
}
case LEVEL_DEBUG : {
logger.debug( logText );
break;
}
case LEVEL_INFO : {
logger.info( logText );
break;
}
case LEVEL_WARN : {
logger.warn( logText );
break;
}
case LEVEL_ERROR : {
logger.error( logText );
break;
}
}

} catch ( Exception e ) {
throw new BoxRuntimeException( "An error occurred while attempting to log the message", e );
} finally {
if ( fileAppender != null ) {
fileAppender.stop();
}
}

return null;
}
}
1 change: 1 addition & 0 deletions src/main/java/ortus/boxlang/runtime/scopes/Key.java
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,7 @@ public class Key implements Comparable<Key>, Serializable {
public static final Key terminate = Key.of( "terminate" );
public static final Key terminated = Key.of( "terminated" );
public static final Key terminateOnTimeout = Key.of( "terminateOnTimeout" );
public static final Key text = Key.of( "text" );
public static final Key textQualifier = Key.of( "textQualifier" );
public static final Key thisTag = Key.of( "thisTag" );
public static final Key thread = Key.of( "thread" );
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/**
* [BoxLang]
*
* Copyright [2023] [Ortus Solutions, Corp]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package ortus.boxlang.runtime.bifs.global.system;

import static org.junit.Assert.assertTrue;

import java.io.PrintStream;
import java.nio.file.Paths;

import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import ortus.boxlang.runtime.BoxRuntime;
import ortus.boxlang.runtime.context.IBoxContext;
import ortus.boxlang.runtime.context.ScriptingRequestBoxContext;
import ortus.boxlang.runtime.dynamic.casters.StringCaster;
import ortus.boxlang.runtime.scopes.IScope;
import ortus.boxlang.runtime.scopes.Key;
import ortus.boxlang.runtime.scopes.VariablesScope;
import ortus.boxlang.runtime.util.FileSystemUtil;

public class WriteLogTest {

static BoxRuntime instance;
static IBoxContext context;
static IScope variables;
static Key result = new Key( "result" );
static ByteArrayOutputStream outContent;
static PrintStream originalOut = System.out;

static String logsDirectory;

@BeforeAll
public static void setUp() {
instance = BoxRuntime.getInstance( true );
logsDirectory = instance.getConfiguration().runtime.logsDirectory;
outContent = new ByteArrayOutputStream();
System.setOut( new PrintStream( outContent ) );
}

@AfterAll
public static void teardown() {
System.setOut( originalOut );

}

@BeforeEach
public void setupEach() {
context = new ScriptingRequestBoxContext( instance.getRuntimeContext() );
variables = context.getScopeNearby( VariablesScope.name );
outContent.reset();
}

@DisplayName( "It can write a default log" )
@Test
public void testPrint() {
instance.executeSource(
"""
writeLog( "Hello Logger!" )
""",
context );
}

@DisplayName( "It can write a default with a custom category" )
@Test
public void testCategory() {
instance.executeSource(
"""
writeLog( text="Hello Logger!", log="Custom" )
""",
context );
}

@DisplayName( "It can write a default with a custom log file" )
@Test
public void testCustomFile() {
String logFilePath = Paths.get( logsDirectory, "/foo.log" ).normalize().toString();
if ( FileSystemUtil.exists( logFilePath ) ) {
FileSystemUtil.deleteFile( logFilePath );
}
instance.executeSource(
"""
writeLog( text="Hello Logger!", log="Foo", file="foo.log" )
""",
context );
assertTrue( FileSystemUtil.exists( logFilePath ) );
String fileContent = StringCaster.cast( FileSystemUtil.read( logFilePath ) );
assertTrue( StringUtils.contains( fileContent, "Hello Logger!" ) );
}

@DisplayName( "It can write a default with a custom log file" )
@Test
public void testCustomFileAndLevel() {
String logFilePath = Paths.get( logsDirectory, "/foo.log" ).normalize().toString();
if ( FileSystemUtil.exists( logFilePath ) ) {
FileSystemUtil.deleteFile( logFilePath );
}
instance.executeSource(
"""
writeLog( text="Hello Error Logger!", log="Foo", file="foo.log", type="Error" );
""",
context );

instance.executeSource(
"""
writeLog( text="Hello Root Logger!" );
""",
context );
assertTrue( FileSystemUtil.exists( logFilePath ) );
String fileContent = StringCaster.cast( FileSystemUtil.read( logFilePath ) );
assertTrue( StringUtils.contains( fileContent, "[ERROR]" ) );
assertTrue( StringUtils.contains( fileContent, "Hello Error Logger!" ) );
}

}

0 comments on commit ab72978

Please sign in to comment.