Description
Blindsight is a Scala logging API that allows for structured logging through DSL and Marker/Argument Type Classes, fluent logging, semantic logging, flow logging, context aware logging, conditional logging, and other useful things.
Blindsight alternatives and similar packages
Based on the "Distributed Systems" category.
Alternatively, view Blindsight alternatives based on common mentions on social networks and blogs.
-
Akka Tracing
A distributed tracing extension for Akka. Provides integration with Play framework, Spray and Akka HTTP.
WorkOS - The modern identity platform for B2B SaaS
Do you think we are missing an alternative of Blindsight or a related project?
Popular Comparisons
README
Blindsight
Suffering in silence, you check the logs for fresh telemetry.
You think: That can't be right.
-- Blindsight, Peter Watts
Blindsight is "observability through logging" where observability is defined as baked in high cardinality structured data with field types. The name is taken from Peter Watts' excellent first contact novel, Blindsight.
Blindsight is a logging library written in Scala that wraps SLF4J to add useful features that solve several outstanding problems with logging:
- Rendering structured logs in multiple formats through a format-independent AST and DSL.
- Managing complex relationships and schema through JSON-LD.
- Expressing domain specific objects as arguments through type classes.
- Resolving operation-specific loggers through logger resolvers.
- Building up complex logging statements through fluent logging.
- Enforcing user supplied type constraints through semantic logging.
- Minimal-overhead tracing and causality tracking through flow logging.
- Providing thread-safe context to logs through context aware logging.
- Time-based and targeted diagnostic logging through conditional logging.
- Hooks into logging entries through entry transformation
- Application accessible debug and trace logs through event buffers
- Method or line based logging overrides at runtime through scripting.
- Easier "printf debugging" through macro based inspections.
See the documentation for more details.
Example
You can check out a "starter project" at https://github.com/tersesystems/blindsight-starter.
There's an example application at https://github.com/tersesystems/play-blindsight that integrates with Honeycomb Tracing using the flow logger:
[trace.png](trace.png)
Dependencies
The only hard dependency is the SLF4J API, but the DSL functionality is only implemented for Logback with logstash-logback-encoder.
Blindsight is a pure SLF4J wrapper: it delegates all logging through to the SLF4J API and does not configure or manage the SLF4J implementation at all.
Versions are published for Scala 2.11, 2.12, 2.13, and 3.0.0.
Install
See Setup for how to install Blindsight.
You can check out a "starter project" at https://github.com/tersesystems/blindsight-starter.
Because Blindsight uses a very recent version of Logstash that depends on Jackson 2.11.0, you may need to update your dependencies for the jackson-scala-module
if you're using Play or Akka.
libraryDependencies += "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.11.0"
Usage
The easiest way to use Blindsight is to import the base package and the DSL:
import com.tersesystems.blindsight._
import com.tersesystems.blindsight.DSL._
To use a Blindsight Logger:
val logger = LoggerFactory.getLogger
logger.info("I am an SLF4J-like logger")
or in block form for diagnostic logging:
logger.debug { debug => debug("I am an SLF4J-like logger") }
import com.tersesystems.blindsight._
import com.tersesystems.blindsight.DSL._
logger.info("Logs with argument {}", bobj("array" -> Seq("one", "two", "three")))
val dayOfWeek = "Monday"
val temp = 72
// macro expands this to:
// Statement("It is {} and the temperature is {} degrees.", Arguments(dayOfWeek, temp))
val statement: Statement = st"It is ${dayOfWeek} and the temperature is ${temp} degrees."
logger.info(statement)
case class Lotto(
id: Long,
winningNumbers: List[Int],
winners: List[Winner],
drawDate: Option[java.util.Date]
) {
lazy val asBObject: BObject = "lotto" ->
("lotto-id" -> id) ~
("winning-numbers" -> winningNumbers) ~
("draw-date" -> drawDate.map(_.toString)) ~
("winners" -> winners.map(w => w.asBObject))
}
object Lotto {
implicit val toArgument: ToArgument[Lotto] = ToArgument { lotto => Argument(lotto.asBObject) }
}
val winners =
List(Winner(23, List(2, 45, 34, 23, 3, 5)), Winner(54, List(52, 3, 12, 11, 18, 22)))
val lotto = Lotto(5, List(2, 45, 34, 23, 7, 5, 3), winners, None)
logger.info("message {}", lotto) // auto-converted to structured output
implicit val nodeObjectToArgument: ToArgument[NodeObject] = ToArgument[NodeObject] { nodeObject =>
Argument(BlindsightASTMapping.toBObject(nodeObject))
}
implicit val nodeObjectToMarkers: ToMarkers[NodeObject] = ToMarkers { nodeObject =>
Markers(BlindsightASTMapping.toBObject(nodeObject))
}
implicit val nodeObjectToStatement: ToStatement[NodeObject] = ...
class Foo extends LDContext { // LDContext contains all the type safe bindings
def sayHello(): Unit = {
val willPerson = NodeObject(
`@type` -> "Person",
`@id` -> willId,
givenName -> "Will",
familyName -> "Sargent",
parent -> parentId,
occupation -> Occupation(
estimatedSalary = MonetaryAmount(Currency.getInstance("USD"), 1),
name = "Code Monkey"
)
)
logger.info("as an argument {}", willPerson) // as an argument
logger.info(Markers(willPerson), "as a marker") // as a marker
logger.semantic[NodeObject].info(willPerson) // or as a statement
}
}
logger.fluent.info
.message("The Magic Words are")
.argument(Arguments("Squeamish", "Ossifrage"))
.logWithPlaceholders()
// log only user events
logger.semantic[UserEvent].info(userEvent)
// Works well with refinement types
import eu.timepit.refined.api.Refined
import eu.timepit.refined.string._
import eu.timepit.refined._
logger.semantic[String Refined Url].info(refineMV(Url)("https://tersesystems.com"))
import com.tersesystems.blindsight.flow._
implicit def flowBehavior[B]: FlowBehavior[B] = new SimpleFlowBehavior
val arg1: Int = 1
val arg2: Int = 2
val result:Int = logger.flow.trace(arg1 + arg2)
logger.withCondition(booleanCondition).info("Only logs when condition is true")
logger.info.when(booleanCondition) { info => info("when true") }
import DSL._
// Add key/value pairs with DSL and return a logger
val markerLogger = logger.withMarker(bobj("userId" -> userId))
// log with generated logger
markerLogger.info("Logging with user id added as a context marker!")
// can retrieve state markers
val contextMarkers: Markers = markerLogger.markers
val logger = LoggerFactory.getLogger
.withEntryTransform(e => e.copy(message = e.message + " IN BED"))
logger.info("You will discover your hidden talents")
val queueBuffer = EventBuffer(1)
val logger = LoggerFactory.getLogger.withEventBuffer(queueBuffer)
logger.info("Hello world")
val event = queueBuffer.head
val scriptHandle = new ScriptHandle {
override def isInvalid: Boolean = false // on file modification, etc
override val script: String =
"""import strings as s from 'std.tf';
|alias s.ends_with? as ends_with?;
|
|library blindsight {
| function evaluate: (long level, string enc, long line, string file) ->
| if (ends_with?(enc, "specialMethodName")) then true
| else false;
|}
|""".stripMargin
override def report(e: Throwable): Unit = e.printStackTrace()
}
val scriptManager = new ScriptManager(scriptHandle)
val location = new ScriptAwareLocation(scriptManager)
def specialMethodName = {
// inside the specialMethodName method here :-)
logger.debug.when(location.here) { log =>
log("script allows selective logging by method or by line")
}
}
import com.tersesystems.blindsight.inspection.InspectionMacros._
decorateIfs(dif => logger.debug(s"${dif.code} = ${dif.result}")) {
if (System.currentTimeMillis() % 17 == 0) {
println("branch 1")
} else if (System.getProperty("derp") == null) {
println("branch 2")
} else {
println("else branch")
}
}
Benchmarks
Benchmarks are available here.
License
Blindsight is released under the "Apache 2" license. See [LICENSE](LICENSE) for specifics and copyright declaration.
*Note that all licence references and agreements mentioned in the Blindsight README section above
are relevant to that project's source code only.