Skip to content

wsargent/blindsight-scripting

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Dynamic Conditions with Scripting (Groovy / Tweakflow)

This project demonstrates how to change logging conditions in a running JVM, using Blindsight.

A condition has to return Boolean but is intentionally left open so that anything can be a condition. This means that we can tie a condition to a JSR 223 script.

In this example, we'll use Groovy to evaluate a condition and return a boolean. If the groovy script changes, then the JVM picks it up and evaluates it without having to restart the JVM.

The groovy script can execute arbitrary logic in the JVM, so it's powerful but may be a security risk. A Tweakflow condition manager is also included, which runs a tightly controlled script that only allows computation on explicit inputs. The TweakFlow script additionally has an HMAC signature that ensures that scripts have not been tampered with.

Main

The main program runs a loop that conditionally logs various statements:

object Main {

  val logger: Logger = LoggerFactory.getLogger(getClass)
  val cm = new ScriptConditionManager(Paths.get("src/main/groovy/condition.groovy"), "groovy")
  //val cm = new TweakFlowConditionManager(Paths.get("src/main/tweakflow/condition.tf"))

  def main(args: Array[String]): Unit = {
    // Run from a loop
    while (true) {
      logInfo()
      logDebug()
      logInfoSpecial()
      logDebugSpecial()

      Thread.sleep(1000L)
    }
  }

  def logInfo(): Unit = {
    logger.info.when(cm.condition()) { info =>
      info("Logging at a info level")
    }
  }

  def logInfoSpecial(): Unit = {
    logger.info.when(cm.condition()) { info =>
      info("Logging at a info level from special method")
    }
  }

  def logDebugSpecial(): Unit = {
    logger.debug.when(cm.condition()) { info =>
      info("Logging at a debug level from special method")
    }
  }

  def logDebug(): Unit = {
    logger.debug.when(cm.condition()) { handle =>
      handle("Logging at a debug level")
    }
  }

}

Groovy Script

There's a single Groovy script, condition.groovy. It runs a single method, which returns a boolean indicating whether logging should happen or not.

import com.tersesystems.blindsight.Markers
import org.slf4j.event.Level
import sourcecode.Enclosing
import sourcecode.File

boolean evaluate(Level level, Markers markers, Enclosing enclosing, File file) {
    // We like this debug message so we want it to show up
    var enc = enclosing.value()
    if (enc == "com.tersesystems.blindsight.scripting.Main.logDebugSpecial") {
        return true;
    }

    // We don't like this info message
    if (enc == "com.tersesystems.blindsight.scripting.Main.logInfoSpecial") {
        return false;
    }

    // Otherwise we'll just use info level.
    return (level.toInt() >= Level.INFO.toInt())
}

The ConditionManager and the FileConditionSource keep track of the file's last modification time. If the file's been modified since last scene, then the script is evaluated again, and the new script is used.

The FileConditionSource is a trivial example for the purposes of demonstration, and a JDBC ConditionSource or Redis ConditionSource could also be used to pull updated script information.

Running

sbt run

And then edit condition.groovy to your preference.

Hit Control-C to cancel the app.