TreeLog
repository·main·Indexed 42 days ago
https://github.com/lancewalton/treelogA Scala library for writing hierarchical logs alongside computations using a Writer monad. It builds a Tree representing a computation's history to help analyze complex, non-linear processes. Supports Scala 2.10.x through 3.6.x, with specific integrations for Scalaz and Cats. Features include DescribedComputation for tracking success/failure, custom annotations via LogTreeLabel, and utilities for serializing and visualizing log trees.
What's inside TreeLog
- TreeLog is designed to capture the hierarchical nature of complex computations. Instead of linear logging, it uses a Writer monad to write to a Tree structure. This allows you to associate log information directly with the values being computed, creating a structured, hierarchical representation of what happened during a computation (what was done, why, and how results were derived).
Install TreeLog for modern Scala
mainTo use TreeLog with Scala 2.13.x or Scala 3.3.x through 3.6.x, add the following dependency to your
libraryDependenciesin your build configuration (e.g.,build.sbt):libraryDependencies ++= Seq("com.casualmiracles" %% "treelog" % "1.9.5")Install TreeLog for older Scala and Scalaz/Cats versions
mainIf you are using older versions of Scala or specific functional programming libraries like Scalaz or Cats, use the corresponding dependency configuration:
Scala 2.12.x and scalaz 7.2.x
libraryDependencies ++= Seq( "com.casualmiracles" %% "treelog-scalaz-72x" % "1.4.3", "org.scalaz" %% "scalaz-core" % "7.2.8")Scala 2.12.x, 2.11.x and scalaz 7.3.x
libraryDependencies ++= Seq( "com.casualmiracles" %% "treelog" % "1.4.10", "org.scalaz" %% "scalaz-core" % "7.3.0-M18")Scala 2.12.x and cats 1.6.0
libraryDependencies ++= Seq("com.casualmiracles" %% "treelog-cats" % "1.4.9")Scala 2.12.x and scalaz 7.1.x
libraryDependencies ++= Seq( "com.casualmiracles" %% "treelog-scalaz-71x" % "1.4.0", "org.scalaz" %% "scalaz-core" % "7.1.11")Scala 2.11.x and scalaz 7.2.x
libraryDependencies ++= Seq( "com.casualmiracles" %% "treelog" % "1.3.0", "org.scalaz" %% "scalaz-core" % "7.2.0")Scala 2.11.x and scalaz 7.1.x
libraryDependencies ++= Seq( "com.casualmiracles" %% "treelog" % "1.2.6", "org.scalaz" %% "scalaz-core" % "7.1.7")Scala 2.10.x
libraryDependencies ++= Seq( "com.casualmiracles" %% "treelog" % "1.2.2", "org.scalaz" %% "scalaz-core" % "7.0.6")Using Booleans as success/failure indicators
mainThe
BooleanSyntaxallows you to treat aBooleanas a signal for the success or failure of a computation.b ~>? description: Iftrue, succeeds withtrueand the description. Iffalse, fails with the description.b.decribedWith(failureDescription, successDescription): Allows different descriptions for thefalseandtruecases.
Syntactic Sugar:
b ~>? (failureDesc, successDesc): Equivalent todecribedWith.
val b = true val dc = b ~>? "Is my boolean true?" // Using different descriptions val dc2 = b ~>? ("It was false", "It was true")Construct a tree using Node and Leaf
mainYou can build a tree structure using the
Tree.NodeandTree.Leafobjects.Tree.Node(root, forest): Creates a node with arootlabel and asubForest(aLazyListof childTreeobjects).Tree.Leaf(root): A convenience method to create a node with no children.
Both constructors use call-by-name parameters (
=> Aand=> LazyList[Tree[A]]), allowing for lazy evaluation of the tree structure.import treelog.Tree // Create a leaf val leaf = Tree.Leaf("end") // Create a node with children val tree = Tree.Node("root", LazyList( Tree.Leaf("child 1"), Tree.Leaf("child 2") ))Folding over values with log tree updates
mainThe
FoldSyntaxallows you to fold over anIterable[V]while accumulating both the result and the log tree.description ~> / (initial, f): Folds overvaluesstarting withinitial. For each element, it appliesf(accumulator, element), which must return aDescribedComputation. The entire process is wrapped in a branch nameddescription.
Note: If any step in the fold results in a failure, the subsequent steps are skipped (due to the monadic nature of
DescribedComputation).val values = List(1, 2, 3) val initial = 0.logSuccess("Start") // Fold: adding each value to the sum, logging each step val result = "Summing" ~>/ (values, initial) { (acc, v) => (acc + v).logSuccess(s"Added $v") }Visualize a tree with drawTree
mainThe
drawTreemethod produces a 2D String representation of the tree, suitable for logging or console output. It uses ASCII-style characters (+-,`,|) to represent branches and stems.Note: This method requires an implicit
Show[A]instance for the typeAcontained in the tree to convert labels into strings.// Assuming an implicit Show[String] is available val visualString = tree.drawTree println(visualString)Serializing DescribedComputations
mainYou can convert a
DescribedComputation[V]into a serializable form to persist the log tree and the result.toSerializableForm(dc): Returns aTuple2(Either[String, V], SerializableTree[Annotation]).fromSerializableForm(sdc): Reconstructs theDescribedComputation[V]from the serializable tuple.
This is useful for saving the state of a long-running computation and its associated log history.
val dc: DescribedComputation[Int] = 42.logSuccess("Done") // Serialize val serialized = toSerializableForm(dc) // Deserialize val restored = fromSerializableForm(serialized)Using Either as success/failure indicators
mainThe
EitherSyntaxconverts anEither[String, V]into aDescribedComputation[V].either ~>? (leftDesc, rightDesc): IfLeft(err), fails withleftDesc(err). IfRight(v), succeeds withvandrightDesc.either ~>? description: IfLeft(err), fails withdescription - err. IfRight(v), succeeds withvanddescription.either ~>? description: IfRight(v), succeeds withvanddescription. IfLeft(err), fails witherr(the error message itself becomes the failure description).
val e: Either[String, Int] = Right(10) // Success with custom description val dc = e ~>? (err => s"Error: $err", v => s"Value is $v")Lifting values into DescribedComputations
mainUse
LogTreeSyntaxto convert raw values intoDescribedComputation[V], which tracks both the result (success/failure) and a log tree of the process.logSuccess(description: String): Creates a successful computation with a given description.logFailure(description: String): Creates a failed computation with a given description.
Syntactic Sugar:
value ~> description: Success with description.value ~>! description: Failure with description.
// Success val success = 1.logSuccess("One") val successSugar = 1 ~> "One" // Failure val failure = 1.logFailure("One") val failureSugar = 1 ~>! "One"Flatten a tree using pre-order traversal
mainThe
flattenmethod performs a pre-order traversal of the tree, returning anEval[LazyList[A]]. This allows you to process all elements in the tree in a single linear sequence.// Returns an Eval containing a LazyList of all elements val elements: Eval[LazyList[String]] = tree.flatten // To access the values: val list = elements.valueUsing Options as success/failure indicators
mainThe
OptionSyntaxconverts anOption[V]into aDescribedComputation[V].option.log(noneDescription, someDescription): IfSome(x), succeeds withxand thesomeDescription. IfNone, fails withnoneDescription.option ~>? (noneDescription, someDescription): Syntactic sugar forlog.option ~>| [B] (f, dflt): IfSome, appliesfto the value; ifNone, returns the provided defaultdfltcomputation.
Syntactic Sugar:
option ~>? description: Uses the same description for bothSomeandNonecases.
val opt: Option[Int] = Some(42) // Success with value included in description val dc = opt.log("Missing value") { x => s"Found $x" } // Simple success/failure val dc2 = opt ~>? "The option should be present"